Compare commits
14 Commits
3919162c6b
...
5bf94b1db4
Author | SHA1 | Date |
---|---|---|
Matthieu Morin | 5bf94b1db4 | |
matthieu42morin | 44ad99d5a7 | |
matthieu42morin | 918a02e6d2 | |
matthieu42morin | 6ac9bb8c03 | |
matthieu42morin | 449f67c2a0 | |
matthieu42morin | 15e7a32263 | |
matthieu42morin | f6a2a7558e | |
matthieu42morin | a34fd5b31e | |
matthieu42morin | c9221c357e | |
matthieu42morin | 64e1e015b0 | |
matthieu42morin | 63ce40cc46 | |
matthieu42morin | 758ab0aeae | |
matthieu42morin | a829327c28 | |
matthieu42morin | 11ec893fa0 |
86
README.md
86
README.md
|
@ -1,38 +1,78 @@
|
|||
# create-svelte
|
||||
|
||||
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
|
||||
This is the repo of the website [Kkosmetickysalon.cz](https://kkosmetickysalon.cz), my mom's business, I also did the logos and branding, which are located in `/static`
|
||||
|
||||
## Creating a project
|
||||
## Webapp stack
|
||||
|
||||
If you're seeing this, you've probably already done this step. Congrats!
|
||||
### [Sveltekit with advanced config](https://kit.svelte.dev/docs/introduction)
|
||||
|
||||
```bash
|
||||
# create a new project in the current directory
|
||||
npm create svelte@latest
|
||||
Sveltekit is simple, modern metaframework for svelte. The docs are great, love the decisions and features, don't need anything more or less for websites with moderate complexity and PWAs.
|
||||
|
||||
# create a new project in my-app
|
||||
npm create svelte@latest my-app
|
||||
```
|
||||
### [Skeleton UI library](https://skeleton.dev)
|
||||
|
||||
## Developing
|
||||
This is perhaps the best UI library for sveltekit, their docs and their code is great
|
||||
|
||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||
### Tailwind + PostCSS + Fontawesome local
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
Utility css is quick to develop, easy to remember, readable little boilerplate needed. Postcss as a dependency and for conditional css. Fontawesome free for local icons as fonts.
|
||||
|
||||
# or start the server and open the app in a new browser tab
|
||||
npm run dev -- --open
|
||||
```
|
||||
### Most importantly, SCREW CMS, use .md or json or yaml and parse them
|
||||
|
||||
## Building
|
||||
#### Motivation
|
||||
|
||||
To create a production version of your app:
|
||||
I have spent considerable amount of time researching and learning the bloatware that is on the internet caused by the nocode movement and enough is enough. It takes a fraction of a time, if any to understand how json works, if you know it's a way to represent and structure content, then you're set.
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
- it's dead simple and you literally almost just import it
|
||||
- You don't have to host strapi or a db on a 4GB RAM server in the cloud for making a post once a month
|
||||
- Static makes better SEO
|
||||
|
||||
You can preview the production build with `npm run preview`.
|
||||
#### Use case
|
||||
|
||||
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
|
||||
I have a $content path (`./src/content`), where I have `./services/` and `./posts` in the former I store individual service categories (and their respective items) in json files, in the latter I have posts, where I store .md files, which are subsequently processed by MDSveX and the html outputted from MDSveX is relayed to `./sluzby/[id]`. It's very DRY, there are no lockins and it's reusable on a lot of platforms.
|
||||
Everything has a schema (e.g. `./sluzby/schema#`) or a type or a generatable template.
|
||||
The schema can be used with a [JSON editor](https://github.com/json-editor/json-editor) to help nontechies write it.
|
||||
The json data is then validated by [ajv](https://ajv.js.org) to match the type Service in `$lib/types/service.d.ts`
|
||||
|
||||
## Features, Components and parts
|
||||
|
||||
A list of mostly
|
||||
|
||||
### [LibreMaps](https://svelte-maplibre.vercel.app/)
|
||||
|
||||
Screw Google Maps, I knew I wanted to use OSM, maybe Mapbox, because I had experience with it, but this was a great ready made, quality solution, repo is [here](https://github.com/dimfeld/svelte-maplibre).
|
||||
|
||||
### [Instagram feed](https://github.com/rodneylab/sveltekit-instagram-infinite-scroll)
|
||||
|
||||
### CSP, custom hooks, custom headers
|
||||
|
||||
Securing this app with the latest security features and web technologies.
|
||||
|
||||
### Service Worker
|
||||
|
||||
Why not?
|
||||
|
||||
## Admin/DevOps Tools
|
||||
|
||||
A list of mostly 3rd party useful tools this project uses.
|
||||
|
||||
### Gitea action CI/CD workflow
|
||||
|
||||
### hCaptcha
|
||||
|
||||
For forms, I may remove this in favor of something else, because it could be a privacy and GDPR issue. Also screw Google and their reCaptcha.
|
||||
|
||||
### Plausible self-hosted
|
||||
|
||||
That or coding some metrics and using some opinionated solution myself.
|
||||
|
||||
### Sentry - runtime prod & dev analysis
|
||||
|
||||
Sentry is cool, I will probably not use 80% of their features, but when doing CSP and all sorts of reporting, this came in very handy. I don't really see an alternative with sveltekit.
|
||||
|
||||
### Playwright - headless browser target testing
|
||||
|
||||
TODO, probably sometime, It can be useful with the service posts.
|
||||
|
||||
### Dockerfile
|
||||
|
||||
Selfhosting this is the only way. I used ansible and terraform to get this thing in the air together with the analytics platform. It's on AWS for now,
|
||||
|
|
18
package.json
18
package.json
|
@ -1,12 +1,15 @@
|
|||
{
|
||||
"name": "cosmeticstudio",
|
||||
"name": "kkosmetickysalon",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"validate": "node ./tests/ValidateServices.js",
|
||||
"dev": "pnpm run validate && vite dev --mode development",
|
||||
"build": "pnpm run validate && vite build",
|
||||
"build-dev": "pnpm run validate && vite build --mode development",
|
||||
"preview": "vite preview",
|
||||
"test": "npm run test:integration && npm run test:unit",
|
||||
"test": "pnpm run test:integration && npm run test:unit",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"lint": "prettier --plugin-search-dir . --check . && eslint .",
|
||||
|
@ -22,7 +25,8 @@
|
|||
"@fortawesome/fontawesome-free": "^6.5.1",
|
||||
"@sentry/sveltekit": "^7.107.0",
|
||||
"@sveltejs/adapter-node": "^5.0.1",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.2"
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.2",
|
||||
"ajv": "^8.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@floating-ui/dom": "1.5.3",
|
||||
|
@ -44,6 +48,7 @@
|
|||
"prettier-plugin-svelte": "^2.10.1",
|
||||
"svelte": "^4.2.12",
|
||||
"svelte-check": "^3.6.7",
|
||||
"svelte-maplibre": "^0.8.2",
|
||||
"svelte-preprocess": "^5.1.3",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"tslib": "^2.6.2",
|
||||
|
@ -51,6 +56,5 @@
|
|||
"vite": "^5.1.6",
|
||||
"vite-plugin-tailwind-purgecss": "^0.2.0",
|
||||
"vitest": "^0.32.4"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"$schema": "/src/routes/sluzby/schema.json",
|
||||
"category": "DALŠÍ VELMI OBLÍBENÉ SLUŽBY",
|
||||
"items": [
|
||||
{
|
||||
"name": "Lifting řas booster (botox)",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace",
|
||||
"id": "lifting-ras-booster",
|
||||
"price": 500,
|
||||
"duration": 1
|
||||
},
|
||||
{
|
||||
"name": "Laminace obočí + výživa",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace",
|
||||
"id": "laminace-oboci-vyziva",
|
||||
"price": 500,
|
||||
"duration": 1
|
||||
},
|
||||
{
|
||||
"name": "Úprava obočí (tvar + barva)",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace",
|
||||
"id": "uprava-oboci-tvar-barva",
|
||||
"price": 250,
|
||||
"duration": 1
|
||||
},
|
||||
{
|
||||
"name": "Úprava obočí + řasy (tvar + barvení)",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace",
|
||||
"id": "uprava-oboci-rasy-tvar-barveni",
|
||||
"price": 300,
|
||||
"duration": 1
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"$schema": "/src/routes/sluzby/schema.json",
|
||||
"category": "Depilace",
|
||||
"items": [
|
||||
{
|
||||
"name": "Depilace Horní ret",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace",
|
||||
"id": "depilace-horni-ret",
|
||||
"price": 80,
|
||||
"duration": 0.5
|
||||
},
|
||||
{
|
||||
"name": "Depilace Brada",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace",
|
||||
"id": "depilace-brada",
|
||||
"price": 80,
|
||||
"duration": 0.5
|
||||
},
|
||||
{
|
||||
"name": "Depilace Obočí",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace",
|
||||
"id": "depilace-oboci",
|
||||
"price": 150,
|
||||
"duration": 0.5
|
||||
},
|
||||
{
|
||||
"name": "Depilace Tváře",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace",
|
||||
"id": "depilace-tvare",
|
||||
"price": 150,
|
||||
"duration": 0.5
|
||||
},
|
||||
{
|
||||
"name": "Depilace Podpaží",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace",
|
||||
"id": "depilace-podpazi",
|
||||
"price": 150,
|
||||
"duration": 0.5
|
||||
},
|
||||
{
|
||||
"name": "Depilace Předloktí",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace",
|
||||
"id": "depilace-predlokti",
|
||||
"price": 200,
|
||||
"duration": 0.5
|
||||
},
|
||||
{
|
||||
"name": "Depilace Celé ruce",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace",
|
||||
"id": "depilace-cele-ruce",
|
||||
"price": 350,
|
||||
"duration": 1
|
||||
},
|
||||
{
|
||||
"name": "Depilace Lýtka",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace",
|
||||
"id": "depilace-lytka",
|
||||
"price": 350,
|
||||
"duration": 1
|
||||
},
|
||||
{
|
||||
"name": "Depilace Celé nohy",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace",
|
||||
"id": "depilace-cele-nohy",
|
||||
"price": 500,
|
||||
"duration": 1
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"$schema": "/src/routes/sluzby/schema.json",
|
||||
"category": "Kosmetické ošetření",
|
||||
"items": [
|
||||
{
|
||||
"name": "ZÁKLADNÍ CALM",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace, enzymatický peeling, kavitační peeling -ultarzvuková špachtle, séra dle typu pleti, masky (tvář,krk,dekolt), závěrečná péče (oční a denní krém)",
|
||||
"id": "zakladni-calm",
|
||||
"price": 500,
|
||||
"duration": 1
|
||||
},
|
||||
{
|
||||
"name": "ZÁKLADNÍ + CALM PLUS",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace, úprava obočí (vosk+pinzeta), barvení řas a obočí, depilace horní ret/brada, enzymatický peeling, kavitační peeling -ultarzvuková špachtle, séra dle typu pleti, masky (tvář,krk,dekolt), závěrečná péče (oční a denní krém)",
|
||||
"id": "zakladni-calm-plus",
|
||||
"price": 600,
|
||||
"duration": 1
|
||||
},
|
||||
{
|
||||
"name": "RELAXAČNÍ",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace, úprava obočí (vosk+pinzeta), barvení řas a obočí, depilace horní ret/brada, enzymatický peeling, kavitační peeling -ultarzvuková špachtle, séra, masáž relaxační (tvář,krk dekolt), masky (tvář,krk,dekolt), závěrečná péče (oční a denní krém)",
|
||||
"id": "relaxacni",
|
||||
"price": 690,
|
||||
"duration": 1.5
|
||||
},
|
||||
{
|
||||
"name": "LIFTINGOVÉ - ANTI AGE",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace, úprava obočí (vosk+pinzeta), barvení řas a obočí, depilace horní ret/brada, enzymatický peeling, kavitační peeling -ultarzvuková špachtle, vacupres ošetření – lifting obličeje krku a dekoltu, séra, masky (tvář,krk,dekolt), alginátová maska, závěrečná péče (oční a denní krém)",
|
||||
"id": "liftingove-anti-age",
|
||||
"price": 690,
|
||||
"duration": 1.5
|
||||
},
|
||||
{
|
||||
"name": "CLEAR + ANTI AKNÉ",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace",
|
||||
"id": "clear-anti-akne",
|
||||
"price": 690,
|
||||
"duration": 1.5
|
||||
},
|
||||
{
|
||||
"name": "Odlíčení + sérum + alginátová maska (PROJASNĚNÍ)",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace",
|
||||
"id": "odliceni-serum-alginatova-maska",
|
||||
"price": 300,
|
||||
"duration": 1
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"$schema": "$routes/sluzby/schema.json",
|
||||
"category": "Permanentní make-up",
|
||||
"items": [
|
||||
{
|
||||
"name": "Obočí Pudrové, Ombré",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace",
|
||||
"id": "oboci",
|
||||
"price": 3000,
|
||||
"duration": 2.5
|
||||
},
|
||||
{
|
||||
"name": "Horní linky - meziřasové přirozené",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace",
|
||||
"id": "linky",
|
||||
"price": 2000,
|
||||
"duration": 2
|
||||
},
|
||||
{
|
||||
"name": "Klasické linky - s ocáskem",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace",
|
||||
"id": "classic-linky",
|
||||
"price": 3000,
|
||||
"duration": 2.5
|
||||
},
|
||||
{
|
||||
"name": "Klasické linky - s ocáskem + spodní linky",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace",
|
||||
"id": "classic-linky+spodni",
|
||||
"price": 3500,
|
||||
"duration": 2.5
|
||||
},
|
||||
{
|
||||
"name": "Rty - kontura",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace",
|
||||
"id": "rty",
|
||||
"price": 2500,
|
||||
"duration": 2
|
||||
},
|
||||
{
|
||||
"name": "3D Rty (kontura a stínování), Full Lips (plné rty)",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace",
|
||||
"id": "3d-rty",
|
||||
"price": 3500,
|
||||
"duration": 2.5
|
||||
},
|
||||
{
|
||||
"name": "Aquarelle Lips (přirodní stínování, bez kontury)",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace",
|
||||
"id": "aquarelle",
|
||||
"price": 3000,
|
||||
"duration": 2
|
||||
},
|
||||
{
|
||||
"name": "První korekce po aplikaci pmu max. do 3 měsíců",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace",
|
||||
"id": "korekce",
|
||||
"price": 1000,
|
||||
"duration": 1.5
|
||||
},
|
||||
{
|
||||
"name": "Oprava práce obočí jiného salonu",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace",
|
||||
"id": "oprava-oboci",
|
||||
"price": "na domluvě",
|
||||
"duration": "na domluvě"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"$schema": "/src/routes/sluzby/schema.json",
|
||||
"category": "Vakuslim 48 - zeštíhlující procedura",
|
||||
"items": [
|
||||
{
|
||||
"name": "Ošetření horních končetin",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace",
|
||||
"id": "vakuslim-48-zestihlujici-procedura-horni-koncetiny",
|
||||
"price": 600,
|
||||
"duration": 2
|
||||
},
|
||||
{
|
||||
"name": "1 ošetření spodní části těla (břicho, boky, dolní končetiny)",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace",
|
||||
"id": "vakuslim-48-zestihlujici-procedura-spodni-cast-tela",
|
||||
"price": 800,
|
||||
"duration": 2
|
||||
},
|
||||
{
|
||||
"name": "1 ošetření komplet horní-dolní části",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace",
|
||||
"id": "vakuslim-48-zestihlujici-procedura-komplet-horni-dolni-cast",
|
||||
"price": 1200,
|
||||
"duration": 2
|
||||
},
|
||||
{
|
||||
"name": "6 ošetření předplatné kompet",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace",
|
||||
"id": "vakuslim-48-zestihlujici-procedura-6-o-setreni-predplatne-kompet",
|
||||
"price": 6600,
|
||||
"duration": 2
|
||||
},
|
||||
{
|
||||
"name": "12 ošetření předplatné komplet",
|
||||
"description": "Diagnostika pleti, odlíčení tonizace",
|
||||
"id": "vakuslim-48-zestihlujici-procedura-12-o-setreni-predplatne-komplet",
|
||||
"price": 11000,
|
||||
"duration": 2
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,25 +1,30 @@
|
|||
// https://gist.github.com/acoyfellow/d8e86979c66ebea25e1643594e38be73, Rodney Lab
|
||||
|
||||
import {
|
||||
PUBLIC_DOMAIN,
|
||||
PUBLIC_SENTRY_KEY,
|
||||
PUBLIC_SENTRY_PROJECT_ID,
|
||||
PUBLIC_SENTRY_ORG_ID,
|
||||
PUBLIC_WORKER_URL,
|
||||
} from '$env/static/public';
|
||||
|
||||
|
||||
const rootDomain = PUBLIC_DOMAIN; // or your server IP for dev
|
||||
export const rootDomain = PUBLIC_DOMAIN; // or your server IP for dev
|
||||
|
||||
const directives = {
|
||||
'base-uri': ["'self'"],
|
||||
'child-src': ["'self'"],
|
||||
'child-src': ["'self'", 'blob:'],
|
||||
// 'connect-src': ["'self'", 'ws://localhost:*'],
|
||||
'connect-src': [
|
||||
"'self'",
|
||||
'ws://localhost:*',
|
||||
'https://*.sentry.io',
|
||||
'https://hcaptcha.com',
|
||||
'https://*.hcaptcha.com',
|
||||
'https://*.cartocdn.com',
|
||||
PUBLIC_DOMAIN,
|
||||
PUBLIC_WORKER_URL,
|
||||
],
|
||||
'img-src': ["'self'", 'data:'],
|
||||
'img-src': ["'self'", 'data:', 'https://images.unsplash.com'],
|
||||
'font-src': ["'self'", 'data:'],
|
||||
'form-action': ["'self'"],
|
||||
'frame-ancestors': ["'self'"],
|
||||
|
@ -30,6 +35,8 @@ const directives = {
|
|||
// "https://*.facebook.net",
|
||||
'https://hcaptcha.com',
|
||||
'https://*.hcaptcha.com',
|
||||
'https://www.openstreetmap.org',
|
||||
'https://*.cartocdn.com'
|
||||
],
|
||||
'manifest-src': ["'self'"],
|
||||
'media-src': ["'self'", 'data:'],
|
||||
|
@ -61,13 +68,20 @@ const directives = {
|
|||
'https://*.hcaptcha.com',
|
||||
'https://*.sentry.io',
|
||||
// 'https://polyfill.io',
|
||||
'https://*.cartocdn.com'
|
||||
],
|
||||
'worker-src': ["'self'"],
|
||||
// remove report-to & report-uri if you do not want to use Sentry reporting
|
||||
'worker-src': [
|
||||
"'self'",
|
||||
'blob:'
|
||||
],
|
||||
//report-to can throw "Content-Security-Policy: Couldn’t process unknown directive ‘report-to’", leave it for older browsers.
|
||||
'report-to': ["'csp-endpoint'"],
|
||||
'report-uri': [
|
||||
`https://sentry.io/api/${PUBLIC_SENTRY_PROJECT_ID}/security/?sentry_key=${PUBLIC_SENTRY_KEY}`,
|
||||
|
||||
`https://${PUBLIC_SENTRY_ORG_ID}.ingest.us.sentry.io/api/${PUBLIC_SENTRY_PROJECT_ID}/security/?sentry_key=${PUBLIC_SENTRY_KEY}`,
|
||||
],
|
||||
};
|
||||
|
||||
export default directives;
|
||||
export const csp = Object.entries(directives)
|
||||
.map(([key, arr]) => key + ' ' + arr.join(' '))
|
||||
.join('; ');
|
|
@ -1,50 +1,47 @@
|
|||
import type { Handle } from '@sveltejs/kit';
|
||||
import { sequence } from "@sveltejs/kit/hooks";
|
||||
import { handleErrorWithSentry, sentryHandle } from "@sentry/sveltekit";
|
||||
import { cspDirectives } from './cspDirectives.js'
|
||||
import * as Sentry from '@sentry/sveltekit';
|
||||
import {
|
||||
PUBLIC_SENTRY_KEY,
|
||||
PUBLIC_SENTRY_PROJECT_ID,
|
||||
PUBLIC_SENTRY_ORG_ID
|
||||
} from '$env/static/public';
|
||||
|
||||
import { csp, rootDomain } from './cspDirectives';
|
||||
|
||||
Sentry.init({
|
||||
dsn: 'https://962a7ed3891a335e112746e5c6c6bf42@o4505828687478784.ingest.us.sentry.io/4506871754326016',
|
||||
tracesSampleRate: 1.0,
|
||||
});
|
||||
|
||||
const csp = Object.entries(cspDirectives)
|
||||
.map(([key, arr]) => key + ' ' + arr.join(' '))
|
||||
.join('; ');
|
||||
|
||||
export const cspHandle = async ({ event, resolve }) => {
|
||||
export const cspHandle: Handle = async ({ event, resolve }) => {
|
||||
if (!csp) {
|
||||
throw new Error('csp is undefined');
|
||||
}
|
||||
const response = await resolve(event);
|
||||
response.headers.set('X-Frame-Options', 'SAMEORIGIN');
|
||||
response.headers.set('Referrer-Policy', 'no-referrer');
|
||||
response.headers.set(
|
||||
'Permissions-Policy',
|
||||
'accelerometer=(), autoplay=(), camera=(), document-domain=(), encrypted-media=(), fullscreen=(), gyroscope=(), interest-cohort=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), sync-xhr=(), usb=(), xr-spatial-tracking=(), geolocation=()',
|
||||
);
|
||||
response.headers.set('X-Content-Type-Options', 'nosniff');
|
||||
/* Switch from Content-Security-Policy-Report-Only to Content-Security-Policy once you are satisifed policy is what you want
|
||||
* on switch comment out the Report-Only line
|
||||
*/
|
||||
response.headers.set('Content-Security-Policy-Report-Only', csp);
|
||||
// response.headers.set('Content-Security-Policy', csp);
|
||||
response.headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
|
||||
response.headers.set(
|
||||
'Expect-CT',
|
||||
`max-age=86400, report-uri="https://sentry.io/api/${PUBLIC_SENTRY_PROJECT_ID}/security/?sentry_key=${PUBLIC_SENTRY_KEY}"`,
|
||||
);
|
||||
response.headers.set(
|
||||
'Report-To',
|
||||
`{group: "csp-endpoint", "max_age": 10886400, "endpoints": [{"url": "https://sentry.io/api/${PUBLIC_SENTRY_PROJECT_ID}/security/?sentry_key=${PUBLIC_SENTRY_KEY}"}]}`,
|
||||
);
|
||||
|
||||
// Permission fullscreen necessary for maps fullscreen
|
||||
const headers = {
|
||||
'X-Frame-Options': 'SAMEORIGIN',
|
||||
'Referrer-Policy': 'no-referrer',
|
||||
'Permissions-Policy': `accelerometer=(), autoplay=(), camera=(), document-domain=(self, 'js-profiling'), encrypted-media=(), fullscreen=(self ${rootDomain}), gyroscope=(), interest-cohort=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), sync-xhr=(), usb=(), xr-spatial-tracking=(), geolocation=()`,
|
||||
'X-Content-Type-Options': 'nosniff',
|
||||
// 'Content-Security-Policy-Report-Only': csp,
|
||||
'Content-Security-Policy': csp,
|
||||
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains; preload',
|
||||
'Expect-CT': `max-age=86400, report-uri="https://${PUBLIC_SENTRY_ORG_ID}.ingest.us.sentry.io/api/${PUBLIC_SENTRY_PROJECT_ID}/security/?sentry_key=${PUBLIC_SENTRY_KEY}"`,
|
||||
'Report-To': `{group: "csp-endpoint", "max_age": 10886400, "endpoints": [{"url": "https://${PUBLIC_SENTRY_ORG_ID}.ingest.us.sentry.io/api/${PUBLIC_SENTRY_PROJECT_ID}/security/?sentry_key=${PUBLIC_SENTRY_KEY}/security/?sentry_key=${PUBLIC_SENTRY_KEY}"}]}`,
|
||||
};
|
||||
|
||||
Object.entries(headers).forEach(([key, value]) => {
|
||||
response.headers.set(key, value);
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
// If you have custom handlers, make sure to place them after `sentryHandle()` in the `sequence` function.
|
||||
export const handle = sequence(sentryHandle(), cspHandle());
|
||||
export const handle: Handle = sequence(sentryHandle(), cspHandle);
|
||||
|
||||
// If you have a custom error handler, pass it to `handleErrorWithSentry`
|
||||
export const handleError = handleErrorWithSentry();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
export let clazz: string = "";
|
||||
export let clazz = "w-8";
|
||||
</script>
|
||||
|
||||
<svg
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
<section class=" sm:inline-flex space-x-1">
|
||||
<a
|
||||
class="brand-icon"
|
||||
class="btn-icon hover:variant-soft-primary"
|
||||
href="{config.instagram}"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
|
@ -32,7 +32,7 @@
|
|||
<i class="fa-brands fa-instagram text-lg" />
|
||||
</a>
|
||||
<a
|
||||
class="brand-icon"
|
||||
class="btn-icon hover:variant-soft-primary"
|
||||
href="{config.facebook}"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
|
@ -53,8 +53,4 @@
|
|||
</AppBar>
|
||||
|
||||
<style lang="postcss">
|
||||
|
||||
.brand-icon {
|
||||
@apply btn-icon hover:variant-soft-primary;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,57 @@
|
|||
<script lang="ts">
|
||||
//class="fill-primary-500 stroke-primary-500"
|
||||
//fill="#FF156D" stroke="#FF156D"
|
||||
</script>
|
||||
<svg class="h-6 w-6 md:h-10 md:w-10" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200"
|
||||
><circle class="fill-primary-500 stroke-primary-500" stroke-width="15" r="15" cx="35" cy="100"
|
||||
><animate
|
||||
attributeName="cx"
|
||||
calcMode="spline"
|
||||
dur="2"
|
||||
values="35;165;165;35;35"
|
||||
keySplines="0 .1 .5 1;0 .1 .5 1;0 .1 .5 1;0 .1 .5 1"
|
||||
repeatCount="indefinite"
|
||||
begin="0"
|
||||
/></circle
|
||||
><circle class="fill-primary-500 stroke-primary-500" stroke-width="15" opacity=".8" r="15" cx="35" cy="100"
|
||||
><animate
|
||||
attributeName="cx"
|
||||
calcMode="spline"
|
||||
dur="2"
|
||||
values="35;165;165;35;35"
|
||||
keySplines="0 .1 .5 1;0 .1 .5 1;0 .1 .5 1;0 .1 .5 1"
|
||||
repeatCount="indefinite"
|
||||
begin="0.05"
|
||||
/></circle
|
||||
><circle class="fill-primary-500 stroke-primary-500" stroke-width="15" opacity=".6" r="15" cx="35" cy="100"
|
||||
><animate
|
||||
attributeName="cx"
|
||||
calcMode="spline"
|
||||
dur="2"
|
||||
values="35;165;165;35;35"
|
||||
keySplines="0 .1 .5 1;0 .1 .5 1;0 .1 .5 1;0 .1 .5 1"
|
||||
repeatCount="indefinite"
|
||||
begin=".1"
|
||||
/></circle
|
||||
><circle class="fill-primary-500 stroke-primary-500" stroke-width="15" opacity=".4" r="15" cx="35" cy="100"
|
||||
><animate
|
||||
attributeName="cx"
|
||||
calcMode="spline"
|
||||
dur="2"
|
||||
values="35;165;165;35;35"
|
||||
keySplines="0 .1 .5 1;0 .1 .5 1;0 .1 .5 1;0 .1 .5 1"
|
||||
repeatCount="indefinite"
|
||||
begin=".15"
|
||||
/></circle
|
||||
><circle class="fill-primary-500 stroke-primary-500" stroke-width="15" opacity=".2" r="15" cx="35" cy="100"
|
||||
><animate
|
||||
attributeName="cx"
|
||||
calcMode="spline"
|
||||
dur="2"
|
||||
values="35;165;165;35;35"
|
||||
keySplines="0 .1 .5 1;0 .1 .5 1;0 .1 .5 1;0 .1 .5 1"
|
||||
repeatCount="indefinite"
|
||||
begin=".2"
|
||||
/></circle
|
||||
></svg
|
||||
>
|
|
@ -1,12 +1,15 @@
|
|||
<script lang="ts">
|
||||
import { getImageLink } from "$lib/images";
|
||||
import { getImageLink } from '$lib/images';
|
||||
import type { Service } from '$lib/types/service';
|
||||
import convertMinutesToHours from '$lib/utils/minToH';
|
||||
|
||||
export let item: Service['items'][number];
|
||||
|
||||
export let item: any = {};
|
||||
</script>
|
||||
|
||||
<a href="/sluzby/{item.id}" class="w-72 card variant-glass-secondary mx-2 my-4 duration-500 hover:scale-105 hover:shadow-xl shadow-md">
|
||||
<a href="/sluzby/{item.id}" id="{item.id}"class="w-72 card variant-glass-secondary mx-2 my-4 duration-500 hover:scale-105 hover:shadow-xl shadow-md">
|
||||
<header>
|
||||
<img src={getImageLink({id: item.id, w: 288, h: 320 })} class="bg-black/50 object-cover aspect-[9/10] rounded-t-xl" alt="Post" />
|
||||
<img src={getImageLink({id: item.id, w: 288, h: 320 })} class="bg-black/50 object-cover aspect-[9/10] rounded-t-xl" alt="Post" loading="lazy" />
|
||||
</header>
|
||||
<!-- <div class="img" style="background-image: url('/images/services/{item.id}.jpg');"/> -->
|
||||
<div class="flex flex-col px-4 py-3 w-72 h-full">
|
||||
|
@ -29,7 +32,8 @@
|
|||
<footer class="justify-self-end align-bottom">
|
||||
<div class="flex flex-col md:flex-row md:justify-between items-center">
|
||||
<p class=" text-lg text-surface-900 font-semibold text-right">
|
||||
{typeof item.price === 'number' ? `${item.price},-` : item.price}{typeof item.duration === 'number' ? `/${item.duration}h` : ''}
|
||||
{typeof item.price === 'number' ? `${item.price},-` : item.price}
|
||||
{typeof item.duration === 'number' ? `/${convertMinutesToHours(item.duration)}` : ''}
|
||||
</p>
|
||||
<a
|
||||
href="https://app.cal.com/kkosmetickysalon/{item.id}"
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
<script lang="ts">
|
||||
import type { Service } from '$lib/types/service';
|
||||
import ServiceCard from '$lib/components/services/ServiceCard.svelte';
|
||||
|
@ -30,13 +31,13 @@
|
|||
|
||||
|
||||
<style lang="postcss">
|
||||
.services-container {
|
||||
/* .services-container {
|
||||
@apply grid md:grid-cols-1 lg:grid-cols-2 2xl:grid-cols-3 p-4;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.services-container {
|
||||
@apply grid-cols-1;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
</style>
|
|
@ -8,3 +8,18 @@ export const author = 'Klára Morinová';
|
|||
export const email = 'klara@kkosmetickysalon.cz';
|
||||
export const facebook = 'https://www.facebook.com/jack.morin.712';
|
||||
export const instagram = 'https://www.instagram.com/kkosmetickysalon/';
|
||||
|
||||
// prettier-ignore
|
||||
export const socialLinks = [
|
||||
{ title: 'Instagram', href: 'https://www.instagram.com/kkosmetickysalon/', icon: 'fa-brands fa-linkedin'},
|
||||
{ title: 'Phone', href: 'tel:+420792304497', icon: './MatrixLogo' },
|
||||
{ title: 'Facebook', href: 'hhttps://www.facebook.com/jack.morin.712', icon: 'fa-brands fa-facebook'},
|
||||
{ title: 'Email', href: 'klara@kkosmetickysalon.cz', icon: 'fa-regular fa-envelope'},
|
||||
];
|
||||
|
||||
// Routes
|
||||
export const NavRoutes = [
|
||||
{ title: 'Home', href: '/' },
|
||||
{ title: 'Sluzby', href: '/Sluzby' },
|
||||
{ title: 'O-mne', href: '/o-mne' }
|
||||
];
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import { writable } from 'svelte/store';
|
||||
import type { Writable } from 'svelte/store';
|
||||
import type { InstagramPost } from '../types/instagram';
|
||||
|
||||
import { derived } from "svelte/store";
|
||||
import { navigating } from "$app/stores";
|
||||
|
||||
|
||||
export const instagramFeed: Writable<InstagramPost[]> = writable([]);
|
||||
|
||||
|
||||
let timer = null;
|
||||
/** When navigating from to, time 500ms to be true, else false */
|
||||
export const navigationIsDelayed = derived(navigating, (newValue, set) => {
|
||||
if (timer) { clearTimeout(timer); }
|
||||
if (newValue) {
|
||||
timer = setTimeout(() => set(true), 500);
|
||||
}
|
||||
set(false);
|
||||
});
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
function convertMinutesToHours(minutes) {
|
||||
try {
|
||||
var hours = Math.floor(minutes / 60);
|
||||
var mins = minutes % 60;
|
||||
if (hours) {
|
||||
console.log("".concat(hours, "h ").concat(mins, "m"));
|
||||
return "".concat(hours, "h ").concat(mins, "m");
|
||||
}
|
||||
else
|
||||
console.log("".concat(mins, "m"));
|
||||
return "".concat(mins, "m");
|
||||
}
|
||||
catch (error) {
|
||||
console.error("I don't think that's time: ".concat(error));
|
||||
process.exit(1); // Exit with non-zero code to signal failure
|
||||
}
|
||||
}
|
||||
exports.default = convertMinutesToHours;
|
|
@ -0,0 +1,16 @@
|
|||
export default function convertMinutesToHours(minutes:number) {
|
||||
try {
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const mins = minutes % 60;
|
||||
if (hours) {
|
||||
console.log(`${hours}h ${mins}m`)
|
||||
return `${hours}h ${mins}m`
|
||||
}
|
||||
|
||||
else console.log(`${mins}m`); return `${mins}m`
|
||||
|
||||
} catch (error) {
|
||||
console.error(`I don't think that's time: ${error}`);
|
||||
process.exit(1); // Exit with non-zero code to signal failure
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<script>
|
||||
import { page } from '$app/stores'
|
||||
</script>
|
||||
|
||||
{#if $page.status === 404}
|
||||
<div class="flex flex-col items-center m-4 py-8">
|
||||
<div
|
||||
class="relative mb-[3.33vh] flex h-24 w-24 md:h-64 md:w-64 items-center justify-center bg-surface-800"
|
||||
>
|
||||
<img
|
||||
class="object-fill"
|
||||
src="/animations/travolta_confused.webp"
|
||||
alt="Travolta Confused gif"
|
||||
/>
|
||||
|
||||
<h1 class="h1 absolute leading-[5rem] text-white">404</h1>
|
||||
</div>
|
||||
<h2 class="h4 mb-2">
|
||||
Bože můj, co tu děláte?
|
||||
</h2>
|
||||
<p>Tato stránka neexistuje, buď byla odstraněna nebo se jednoduše vytratila z vesmíru.</p>
|
||||
</div>
|
||||
{:else}
|
||||
<h2>{$page.status}</h2>
|
||||
|
||||
{#if $page.error}
|
||||
<p class="subhead">{$page.error.message}</p>
|
||||
{/if}
|
||||
|
||||
<p><strong>Promiňte!</strong> Stal se strašlivý omyl. Možná byste mohli zkusit nějaké z následujících stránek?</p>
|
||||
<ul>
|
||||
<li><a href="/">Domů</a></li>
|
||||
<li><a href="/sluzby">Služby</a></li>
|
||||
<li><a href="/kontakt">Projects</a></li>
|
||||
</ul>
|
||||
{/if}
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
|
||||
// Styleshit
|
||||
import '../app.postcss';
|
||||
import '$src/app.postcss';
|
||||
|
||||
// Font Gruesome
|
||||
import '@fortawesome/fontawesome-free/css/fontawesome.css';
|
||||
|
@ -9,7 +9,12 @@
|
|||
import '@fortawesome/fontawesome-free/css/solid.css';
|
||||
|
||||
// Components & Utilities
|
||||
import { AppShell } from '@skeletonlabs/skeleton';
|
||||
import { AppShell, Drawer, initializeStores, getDrawerStore } from '@skeletonlabs/skeleton';
|
||||
import { } from '@skeletonlabs/skeleton';
|
||||
|
||||
initializeStores();
|
||||
const drawerStore = getDrawerStore();
|
||||
$: positionClasses = $drawerStore.open ? 'translate-x-[50%]' : '';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
// Floating UI for Popups
|
||||
|
@ -42,12 +47,20 @@
|
|||
}
|
||||
};
|
||||
|
||||
// Scroll heading into view
|
||||
function scrollHeadingIntoView(): void {
|
||||
if (!window.location.hash) return;
|
||||
const elemTarget: HTMLElement | null = document.querySelector(window.location.hash);
|
||||
if (elemTarget) elemTarget.scrollIntoView({ behavior: 'smooth' });
|
||||
|
||||
// Scroll to anchor
|
||||
$: if ($page.url.pathname) {
|
||||
// Workaround until https://github.com/sveltejs/kit/issues/2664 is fixed
|
||||
if (typeof window !== 'undefined' && window.location.hash) {
|
||||
const deepLinkedElement = document.getElementById(window.location.hash.substring(1));
|
||||
if (deepLinkedElement) {
|
||||
deepLinkedElement.scrollIntoView();
|
||||
}
|
||||
}
|
||||
};
|
||||
import { fade } from 'svelte/transition';
|
||||
import Spinner from '$lib/components/Spinner.svelte';
|
||||
import { navigationIsDelayed } from '$lib/stores';
|
||||
</script>
|
||||
|
||||
<!-- SEO -->
|
||||
|
@ -80,7 +93,15 @@
|
|||
|
||||
<!-- <Analytics /> -->
|
||||
<!-- App Shell -->
|
||||
<AppShell>
|
||||
<Drawer />
|
||||
{#if $navigationIsDelayed}
|
||||
<div class="fixed w-full h-full z-10" in:fade={{ duration: 150 }}>
|
||||
<div class="absolute w-full h-full flex justify-center items-center z-20">
|
||||
<Spinner />
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<AppShell class="transition-transform {positionClasses}">
|
||||
<svelte:fragment slot="header">
|
||||
<MainHeader />
|
||||
</svelte:fragment>
|
||||
|
@ -92,6 +113,7 @@
|
|||
<MainFooter />
|
||||
</svelte:fragment>
|
||||
</AppShell>
|
||||
{/if}
|
||||
<!-- <CookieConsent />
|
||||
|
||||
<Segment /> -->
|
|
@ -1,13 +1,20 @@
|
|||
import { error } from '@sveltejs/kit';
|
||||
import { IG_API_KEY } from '$env/static/private';
|
||||
import { RequestHandler} from './$types';
|
||||
|
||||
export async function GET() {
|
||||
export const POST: RequestHandler = async ({ request }) => {
|
||||
try {
|
||||
const response = await fetch(`https://graph.instagram.com/me/media?fields=id,media_type,media_url,caption,timestamp&access_token=${IG_API_KEY}`);
|
||||
const { next } = await request.json();
|
||||
const response = await fetch(next, {
|
||||
method: 'GET',
|
||||
});
|
||||
const data = await response.json();
|
||||
return new Response(JSON.stringify(data));
|
||||
return new Response(data, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.log('Error: ', err);
|
||||
throw error(500, 'Error retrieving Instagram data');
|
||||
error(500, 'Error retrieving data in /api/instagram.json');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
// === THANKS TO https://github.com/rodneylab/sveltekit-hcaptcha-form/blob/main/src/routes/api/verify/%2Bserver.js
|
||||
// I cleaned it and converted to TS
|
||||
import { HCAPTCHA_SECRETKEY } from '$env/static/private';
|
||||
import { PUBLIC_HCAPTCHA_SITEKEY } from '$env/static/public';
|
||||
import { RequestHandler } from './$types';
|
||||
|
||||
export const POST: RequestHandler = async ({ request }) => {
|
||||
try {
|
||||
const { name, email, message, response: hCaptchaClientResponse } = await request.json();
|
||||
|
||||
// const secret = HCAPTCHA_SECRETKEY;
|
||||
// const sitekey = PUBLIC_HCAPTCHA_SITEKEY;
|
||||
const body = new URLSearchParams({ response: hCaptchaClientResponse, HCAPTCHA_SECRETKEY, PUBLIC_HCAPTCHA_SITEKEY });
|
||||
|
||||
const response = await fetch('https://hcaptcha.com/siteverify', {
|
||||
method: 'POST',
|
||||
credentials: 'omit',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: body.toString(),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
const { success } = data;
|
||||
console.log('data: ', data);
|
||||
if (success) {
|
||||
console.log('hCaptcha says yes!');
|
||||
} else {
|
||||
console.log('hCaptcha says no!');
|
||||
}
|
||||
|
||||
// process name, email and message here e.g. email site admin with message details
|
||||
console.log({ name, email, message });
|
||||
|
||||
return new Response('OK');
|
||||
} catch (err) {
|
||||
const error = `HCaptcha Error in /verify.json.js: ${err}`;
|
||||
console.error(error);
|
||||
return {
|
||||
status: 500,
|
||||
error,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import { error } from '@sveltejs/kit';
|
||||
import { IG_ACCESS_TOKEN } from '$env/static/private';
|
||||
import { PageServerLoad} from './$types';
|
||||
|
||||
|
||||
export const load: PageServerLoad = async () => {
|
||||
try {
|
||||
const url = `https://graph.instagram.com/me/media?fields=caption,id,media_type,media_url,timestamp&access_token=${IG_ACCESS_TOKEN}`;
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
});
|
||||
const data = await response.json();
|
||||
return new Response(data, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.log('Error: ', err);
|
||||
throw error(500, 'Error retrieving Instagram data');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { PUBLIC_MAPBOX_ACCESS_TOKEN } from '$env/static/public';
|
||||
|
||||
|
||||
</script>
|
|
@ -0,0 +1,97 @@
|
|||
<!--
|
||||
This is just a very simple page with a button to throw an example error.
|
||||
Feel free to delete this file and the entire sentry route.
|
||||
-->
|
||||
|
||||
<script>
|
||||
import * as Sentry from '@sentry/sveltekit';
|
||||
|
||||
function getSentryData() {
|
||||
Sentry.startSpan({
|
||||
name: 'Example Frontend Span',
|
||||
op: 'test',
|
||||
}, async () => {
|
||||
const res = await fetch('/sentry-example');
|
||||
if (!res.ok) {
|
||||
throw new Error('Sentry Example Frontend Error');
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<head>
|
||||
<title>Sentry Onboarding</title>
|
||||
<meta name="description" content="Test Sentry for your SvelteKit app!" />
|
||||
</head>
|
||||
|
||||
<main>
|
||||
<h1>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 44">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M124.32,28.28,109.56,9.22h-3.68V34.77h3.73V15.19l15.18,19.58h3.26V9.22h-3.73ZM87.15,23.54h13.23V20.22H87.14V12.53h14.93V9.21H83.34V34.77h18.92V31.45H87.14ZM71.59,20.3h0C66.44,19.06,65,18.08,65,15.7c0-2.14,1.89-3.59,4.71-3.59a12.06,12.06,0,0,1,7.07,2.55l2-2.83a14.1,14.1,0,0,0-9-3c-5.06,0-8.59,3-8.59,7.27,0,4.6,3,6.19,8.46,7.52C74.51,24.74,76,25.78,76,28.11s-2,3.77-5.09,3.77a12.34,12.34,0,0,1-8.3-3.26l-2.25,2.69a15.94,15.94,0,0,0,10.42,3.85c5.48,0,9-2.95,9-7.51C79.75,23.79,77.47,21.72,71.59,20.3ZM195.7,9.22l-7.69,12-7.64-12h-4.46L186,24.67V34.78h3.84V24.55L200,9.22Zm-64.63,3.46h8.37v22.1h3.84V12.68h8.37V9.22H131.08ZM169.41,24.8c3.86-1.07,6-3.77,6-7.63,0-4.91-3.59-8-9.38-8H154.67V34.76h3.8V25.58h6.45l6.48,9.2h4.44l-7-9.82Zm-10.95-2.5V12.6h7.17c3.74,0,5.88,1.77,5.88,4.84s-2.29,4.86-5.84,4.86Z M29,2.26a4.67,4.67,0,0,0-8,0L14.42,13.53A32.21,32.21,0,0,1,32.17,40.19H27.55A27.68,27.68,0,0,0,12.09,17.47L6,28a15.92,15.92,0,0,1,9.23,12.17H4.62A.76.76,0,0,1,4,39.06l2.94-5a10.74,10.74,0,0,0-3.36-1.9l-2.91,5a4.54,4.54,0,0,0,1.69,6.24A4.66,4.66,0,0,0,4.62,44H19.15a19.4,19.4,0,0,0-8-17.31l2.31-4A23.87,23.87,0,0,1,23.76,44H36.07a35.88,35.88,0,0,0-16.41-31.8l4.67-8a.77.77,0,0,1,1.05-.27c.53.29,20.29,34.77,20.66,35.17a.76.76,0,0,1-.68,1.13H40.6q.09,1.91,0,3.81h4.78A4.59,4.59,0,0,0,50,39.43a4.49,4.49,0,0,0-.62-2.28Z"
|
||||
/>
|
||||
</svg>
|
||||
</h1>
|
||||
<p>
|
||||
Get Started with this <strong>simple Example:</strong>
|
||||
</p>
|
||||
|
||||
<p>1. Send us a sample error:</p>
|
||||
<button
|
||||
type="button"
|
||||
on:click={getSentryData}>
|
||||
Throw error!
|
||||
</button>
|
||||
|
||||
<p>
|
||||
2. Look for the error on the
|
||||
<a href="https://none-b0c3fadae.sentry.io/issues/?project=4506871754326016">Issues Page</a>.
|
||||
</p>
|
||||
<p style="margin-top: 24px;">
|
||||
For more information, take a look at the
|
||||
<a href="https://docs.sentry.io/platforms/javascript/guides/sveltekit/">
|
||||
Sentry SvelteKit Documentation
|
||||
</a>
|
||||
</p>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 4rem;
|
||||
margin: 14px 0;
|
||||
}
|
||||
|
||||
svg {
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 12px;
|
||||
cursor: pointer;
|
||||
background-color: rgb(54, 45, 89);
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 1em;
|
||||
margin: 1em;
|
||||
transition: all 0.25s ease-in-out;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #8c5393;
|
||||
box-shadow: 4px;
|
||||
box-shadow: 0px 0px 15px 2px rgba(140, 83, 147, 0.5);
|
||||
}
|
||||
button:active {
|
||||
background-color: #c73852;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,6 @@
|
|||
// This is just a very simple API route that throws an example error.
|
||||
// Feel free to delete this file and the entire sentry route.
|
||||
|
||||
export const GET = async () => {
|
||||
throw new Error("Sentry Example API Route Error");
|
||||
};
|
|
@ -1,22 +1,22 @@
|
|||
<script lang="ts">
|
||||
import ServicesLayout from '$lib/components/services/ServicesLayout.svelte';
|
||||
import type { Service } from '$lib/types/service';
|
||||
let id: string;
|
||||
export const id = '';
|
||||
|
||||
// Locally populated services
|
||||
let services: Service[] = [
|
||||
{
|
||||
category: 'Permanentní make-up',
|
||||
items: [
|
||||
{ name: 'Obočí Pudrové, Ombré', id: 'oboci',price: 3000, duration: 2.5 },
|
||||
{ name: 'Horní linky - meziřasové přirozené', id: 'linky', price: 2000, duration: 2 },
|
||||
{ name: 'Klasické linky - s ocáskem', id: 'classic-linky', price: 3000, duration: 2.5 },
|
||||
{ name: 'Klasické linky - s ocáskem + spodní linky', id: 'classic-linky+spodni', price: 3500, duration: 2.5 },
|
||||
{ name: 'Rty - kontura', id: 'rty', price: 2500, duration: 2 },
|
||||
{ name: '3D Rty (kontura a stínování), Full Lips (plné rty)', id: '3d-rty', price: 3500, duration: 2.5 },
|
||||
{ name: 'Aquarelle Lips (přirodní stínování, bez kontury)', id: 'aquarelle', price: 3000, duration: 2 },
|
||||
{ name: 'První korekce po aplikaci pmu max. do 3 měsíců', id: 'korekce', price: 1000, duration: 1.5 },
|
||||
{ name: 'Oprava práce obočí jiného salonu', id: 'oprava-oboci', price: 'na domluvě', duration: 'na domluvě' }
|
||||
{ name: 'Obočí Pudrové, Ombré', description: 'Diagnostika pleti, odlíčení tonizace', id: 'oboci',price: 3000, duration: 2.5 },
|
||||
{ name: 'Horní linky - meziřasové přirozené', description: 'Diagnostika pleti, odlíčení tonizace', id: 'linky', price: 2000, duration: 2 },
|
||||
{ name: 'Klasické linky - s ocáskem', description: 'Diagnostika pleti, odlíčení tonizace', id: 'classic-linky', price: 3000, duration: 2.5 },
|
||||
{ name: 'Klasické linky - s ocáskem + spodní linky', description: 'Diagnostika pleti, odlíčení tonizace', id: 'classic-linky+spodni', price: 3500, duration: 2.5 },
|
||||
{ name: 'Rty - kontura', description: 'Diagnostika pleti, odlíčení tonizace', id: 'rty', price: 2500, duration: 2 },
|
||||
{ name: '3D Rty (kontura a stínování), Full Lips (plné rty)', description: 'Diagnostika pleti, odlíčení tonizace', id: '3d-rty', price: 3500, duration: 2.5 },
|
||||
{ name: 'Aquarelle Lips (přirodní stínování, bez kontury)', description: 'Diagnostika pleti, odlíčení tonizace', id: 'aquarelle', price: 3000, duration: 2 },
|
||||
{ name: 'První korekce po aplikaci pmu max. do 3 měsíců', description: 'Diagnostika pleti, odlíčení tonizace', id: 'korekce', price: 1000, duration: 1.5 },
|
||||
{ name: 'Oprava práce obočí jiného salonu', description: 'Diagnostika pleti, odlíčení tonizace', id: 'oprava-oboci', price: 'na domluvě', duration: 'na domluvě' }
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -26,41 +26,41 @@
|
|||
{ name: 'ZÁKLADNÍ + CALM PLUS', description:'Diagnostika pleti, odlíčení tonizace, úprava obočí (vosk+pinzeta), barvení řas a obočí, depilace horní ret/brada, enzymatický peeling, kavitační peeling -ultarzvuková špachtle, séra dle typu pleti, masky (tvář,krk,dekolt), závěrečná péče (oční a denní krém)', id: 'zakladni-calm-plus', price: 600, duration: 1 },
|
||||
{ name: 'RELAXAČNÍ', id: 'relaxacni', description:'Diagnostika pleti, odlíčení tonizace, úprava obočí (vosk+pinzeta), barvení řas a obočí, depilace horní ret/brada, enzymatický peeling, kavitační peeling -ultarzvuková špachtle, séra, masáž relaxační (tvář,krk dekolt), masky (tvář,krk,dekolt), závěrečná péče (oční a denní krém)', price: 690, duration: 1.5 },
|
||||
{ name: 'LIFTINGOVÉ - ANTI AGE', description:'Diagnostika pleti, odlíčení tonizace, úprava obočí (vosk+pinzeta), barvení řas a obočí, depilace horní ret/brada, enzymatický peeling, kavitační peeling -ultarzvuková špachtle, vacupres ošetření – lifting obličeje krku a dekoltu, séra, masky (tvář,krk,dekolt), alginátová maska, závěrečná péče (oční a denní krém)', id: 'liftingove-anti-age', price: 690, duration: 1.5 },
|
||||
{ name: 'CLEAR + ANTI AKNÉ', id: 'clear-anti-akne', price: 690, duration: 1.5 },
|
||||
{ name: 'Odlíčení + sérum + alginátová maska (PROJASNĚNÍ)', id: 'odliceni-serum-alginatova-maska', price: 300, duration: 1 },
|
||||
{ name: 'CLEAR + ANTI AKNÉ', description: 'Diagnostika pleti, odlíčení tonizace', id: 'clear-anti-akne', price: 690, duration: 1.5 },
|
||||
{ name: 'Odlíčení + sérum + alginátová maska (PROJASNĚNÍ)', description: 'Diagnostika pleti, odlíčení tonizace', id: 'odliceni-serum-alginatova-maska', price: 300, duration: 1 },
|
||||
]
|
||||
},
|
||||
{
|
||||
category: 'DALŠÍ VELMI OBLÍBENÉ SLUŽBY',
|
||||
items: [
|
||||
{ name: 'Lifting řas booster (botox)', id: 'lifting-ras-booster', price: 500, duration: 1 },
|
||||
{ name: 'Laminace obočí + výživa', id: 'laminace-oboci-vyziva', price: 500, duration: 1 },
|
||||
{ name: 'Úprava obočí (tvar + barva)', id: 'uprava-oboci-tvar-barva', price: 250, duration: 1 },
|
||||
{ name: 'Úprava obočí + řasy (tvar + barvení)', id: 'uprava-oboci-rasy-tvar-barveni', price: 300, duration: 1 },
|
||||
{ name: 'Lifting řas booster (botox)', description: 'Diagnostika pleti, odlíčení tonizace', id: 'lifting-ras-booster', price: 500, duration: 1 },
|
||||
{ name: 'Laminace obočí + výživa', description: 'Diagnostika pleti, odlíčení tonizace', id: 'laminace-oboci-vyziva', price: 500, duration: 1 },
|
||||
{ name: 'Úprava obočí (tvar + barva)', description: 'Diagnostika pleti, odlíčení tonizace', id: 'uprava-oboci-tvar-barva', price: 250, duration: 1 },
|
||||
{ name: 'Úprava obočí + řasy (tvar + barvení)', description: 'Diagnostika pleti, odlíčení tonizace', id: 'uprava-oboci-rasy-tvar-barveni', price: 300, duration: 1 },
|
||||
]
|
||||
},
|
||||
{
|
||||
category: 'Depilace',
|
||||
items: [
|
||||
{ name: 'Depilace Horní ret', id: 'depilace-horni-ret', price: 80, duration: 0.5 },
|
||||
{ name: 'Depilace Brada', id: 'depilace-brada', price: 80, duration: 0.5 },
|
||||
{ name: 'Depilace Obočí', id: 'depilace-oboci', price: 150, duration: 0.5 },
|
||||
{ name: 'Depilace Tváře', id: 'depilace-tvare', price: 150, duration: 0.5 },
|
||||
{ name: 'Depilace Podpaží', id: 'depilace-podpazi', price: 150, duration: 0.5 },
|
||||
{ name: 'Depilace Předloktí', id: 'depilace-predlokti', price: 200, duration: 0.5 },
|
||||
{ name: 'Depilace Celé ruce', id: 'depilace-cele-ruce', price: 350, duration: 1 },
|
||||
{ name: 'Depilace Lýtka', id: 'depilace-lytka', price: 350, duration: 1 },
|
||||
{ name: 'Depilace Celé nohy', id: 'depilace-cele-nohy', price: 500, duration: 1 }
|
||||
{ name: 'Depilace Horní ret', description: 'Diagnostika pleti, odlíčení tonizace', id: 'depilace-horni-ret', price: 80, duration: 0.5 },
|
||||
{ name: 'Depilace Brada', description: 'Diagnostika pleti, odlíčení tonizace', id: 'depilace-brada', price: 80, duration: 0.5 },
|
||||
{ name: 'Depilace Obočí', description: 'Diagnostika pleti, odlíčení tonizace', id: 'depilace-oboci', price: 150, duration: 0.5 },
|
||||
{ name: 'Depilace Tváře', description: 'Diagnostika pleti, odlíčení tonizace', id: 'depilace-tvare', price: 150, duration: 0.5 },
|
||||
{ name: 'Depilace Podpaží', description: 'Diagnostika pleti, odlíčení tonizace', id: 'depilace-podpazi', price: 150, duration: 0.5 },
|
||||
{ name: 'Depilace Předloktí', description: 'Diagnostika pleti, odlíčení tonizace', id: 'depilace-predlokti', price: 200, duration: 0.5 },
|
||||
{ name: 'Depilace Celé ruce', description: 'Diagnostika pleti, odlíčení tonizace', id: 'depilace-cele-ruce', price: 350, duration: 1 },
|
||||
{ name: 'Depilace Lýtka', description: 'Diagnostika pleti, odlíčení tonizace', id: 'depilace-lytka', price: 350, duration: 1 },
|
||||
{ name: 'Depilace Celé nohy', description: 'Diagnostika pleti, odlíčení tonizace', id: 'depilace-cele-nohy', price: 500, duration: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
category: 'Vakuslim 48 - zeštíhlující procedura',
|
||||
items: [
|
||||
{ name: 'Ošetření horních končetin', id: 'vakuslim-48-zestihlujici-procedura-horni-koncetiny', price: 600, duration: 2 },
|
||||
{ name: '1 ošetření spodní části těla (břicho, boky, dolní končetiny)', id: 'vakuslim-48-zestihlujici-procedura-spodni-cast-tela', price: 800, duration: 2 },
|
||||
{ name: '1 ošetření komplet horní-dolní části', id: 'vakuslim-48-zestihlujici-procedura-komplet-horni-dolni-cast', price: 1200, duration: 2 },
|
||||
{ name: '6 ošetření předplatné kompet', id: 'vakuslim-48-zestihlujici-procedura-6-o-setreni-predplatne-kompet', price: 6600, duration: 2 },
|
||||
{ name: '12 ošetření předplatné komplet', id: 'vakuslim-48-zestihlujici-procedura-12-o-setreni-predplatne-komplet', price: 11000, duration: 2 }
|
||||
{ name: 'Ošetření horních končetin', description: 'Diagnostika pleti, odlíčení tonizace', id: 'vakuslim-48-zestihlujici-procedura-horni-koncetiny', price: 600, duration: 2 },
|
||||
{ name: '1 ošetření spodní části těla (břicho, boky, dolní končetiny)', description: 'Diagnostika pleti, odlíčení tonizace', id: 'vakuslim-48-zestihlujici-procedura-spodni-cast-tela', price: 800, duration: 2 },
|
||||
{ name: '1 ošetření komplet horní-dolní části', description: 'Diagnostika pleti, odlíčení tonizace', id: 'vakuslim-48-zestihlujici-procedura-komplet-horni-dolni-cast', price: 1200, duration: 2 },
|
||||
{ name: '6 ošetření předplatné kompet', description: 'Diagnostika pleti, odlíčení tonizace', id: 'vakuslim-48-zestihlujici-procedura-6-o-setreni-predplatne-kompet', price: 6600, duration: 2 },
|
||||
{ name: '12 ošetření předplatné komplet', description: 'Diagnostika pleti, odlíčení tonizace', id: 'vakuslim-48-zestihlujici-procedura-12-o-setreni-predplatne-komplet', price: 11000, duration: 2 }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Service",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"category",
|
||||
"items"
|
||||
],
|
||||
"properties": {
|
||||
"category": {
|
||||
"type": "string",
|
||||
"description": "Name of the category of services",
|
||||
"minLength": 4,
|
||||
"default": "Permanentní makeup"
|
||||
},
|
||||
"items": {
|
||||
"title": "Items under the category",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"title": "Items",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Name of the service"
|
||||
},
|
||||
"description": {
|
||||
"title": "Description",
|
||||
"type": "string",
|
||||
"description": "description :D",
|
||||
"default": "A description of a description"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "a short handle used in urls and on cal.com",
|
||||
"default": "pmu",
|
||||
"minLength": 3
|
||||
},
|
||||
"price": {
|
||||
"title": "Price",
|
||||
"description": "Price of the product",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "number",
|
||||
"description": "Price of the service as a number",
|
||||
"default": 300
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Price of the service as a text description (e.g., 'Free')",
|
||||
"default": "na dohode"
|
||||
}
|
||||
]
|
||||
},
|
||||
"duration": {
|
||||
"title": "Duration",
|
||||
"description": "How long will the procedure take? (can be on demand or specific amount)",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "number",
|
||||
"description": "Duration of the service in minutes (e.g., 60)",
|
||||
"minimum": 15
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Duration of the service as a text description (e.g., 'on demand')"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 734 KiB |
|
@ -0,0 +1,35 @@
|
|||
import adapter from '@sveltejs/adapter-node';
|
||||
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({
|
||||
out: 'build',
|
||||
precompress: false
|
||||
}),
|
||||
|
||||
// Aliases need tsconfig explicit inclusion
|
||||
alias: {
|
||||
$lib: './src/lib',
|
||||
$root: './',
|
||||
$src: './src',
|
||||
$routes: './src/routes',
|
||||
},
|
||||
env: {
|
||||
publicPrefix: "PUBLIC_",
|
||||
},
|
||||
|
||||
// https://kit.svelte.dev/docs/configuration#alias
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
export default config;
|
|
@ -1,32 +0,0 @@
|
|||
import adapter from '@sveltejs/adapter-node';
|
||||
import { vitePreprocess } from '@sveltejs/kit/vite';
|
||||
import preprocess from 'svelte-preprocess';
|
||||
import { join, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { Config } from '@sveltejs/kit';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
|
||||
const config: Config = {
|
||||
// Consult https://github.com/sveltejs/svelte-preprocess
|
||||
// for more information about preprocessors
|
||||
preprocess: [
|
||||
preprocess({
|
||||
postcss: true
|
||||
}),
|
||||
vitePreprocess({
|
||||
style: {
|
||||
css: {
|
||||
postcss: join(__dirname, 'postcss.config.cjs')
|
||||
}
|
||||
}
|
||||
})
|
||||
],
|
||||
kit: {
|
||||
adapter: adapter()
|
||||
// https://kit.svelte.dev/docs/configuration#alias
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
|
@ -0,0 +1,41 @@
|
|||
import Ajv from 'ajv';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const serviceSchema = JSON.parse(fs.readFileSync('./src/routes/sluzby/schema.json', 'utf-8'));
|
||||
|
||||
|
||||
function validateFile(filePath) {
|
||||
const ajv = new Ajv();
|
||||
const data = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
||||
const validate = ajv.compile(serviceSchema);
|
||||
const valid = validate(data);
|
||||
if (!valid) {
|
||||
throw new Error(`Invalid service data in ${filePath}: ${JSON.stringify(validate.errors)}`);
|
||||
}
|
||||
}
|
||||
|
||||
function scanDirectory(directory) {
|
||||
const files = fs.readdirSync(directory);
|
||||
for (const file of files) {
|
||||
const filePath = path.join(directory, file);
|
||||
const stat = fs.statSync(filePath);
|
||||
if (stat.isDirectory()) {
|
||||
scanDirectory(filePath);
|
||||
} else if (path.extname(filePath) === '.json') {
|
||||
validateFile(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function validateServices() {
|
||||
try {
|
||||
scanDirectory('./src/content/services');
|
||||
console.log('All services validated successfully!');
|
||||
} catch (error) {
|
||||
console.error(`Error validating services: ${error}`);
|
||||
process.exit(1); // Exit with non-zero code to signal failure
|
||||
}
|
||||
}
|
||||
|
||||
validateServices();
|
|
@ -8,8 +8,27 @@
|
|||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true
|
||||
}
|
||||
"strict": true,
|
||||
// custom compiler options
|
||||
"noEmit": true,
|
||||
"target": "ES2018",
|
||||
"module": "ES2022",
|
||||
"moduleResolution": "Bundler",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
},
|
||||
"include": [
|
||||
"./scripts/**/*",
|
||||
"./test/*.js",
|
||||
"./*.js",
|
||||
"./src/**/*.d.ts",
|
||||
"./src/**/*.js",
|
||||
"./src/**/*.svelte",
|
||||
"./src/**/*.ts",
|
||||
".svelte-kit/ambient.d.ts",
|
||||
".svelte-kit/types/**/$types.d.ts",
|
||||
"./csp-directives.ts"
|
||||
],
|
||||
"exclude": ["node_modules/*"]
|
||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||
//
|
||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||
|
|
|
@ -12,15 +12,23 @@ export default defineConfig({
|
|||
envPrefix: "PUBLIC_",
|
||||
plugins: [sentrySvelteKit({
|
||||
sourceMapsUploadOptions: {
|
||||
org: "none-b0c3fadae",
|
||||
project: "javascript-sveltekit"
|
||||
org: "mattmor",
|
||||
project: "kkosmetickysalon",
|
||||
|
||||
//telemetry off
|
||||
telemetry: false,
|
||||
}
|
||||
}), sveltekit(), purgeCss({
|
||||
}),
|
||||
sveltekit(),
|
||||
purgeCss({
|
||||
safelist: {
|
||||
// any selectors that begin with "hljs-" will not be purged
|
||||
greedy: [/^hljs-/],
|
||||
},
|
||||
})],
|
||||
define: {
|
||||
'process.env.VITE_BUILD_TIME': JSON.stringify(new Date().toISOString()),
|
||||
},
|
||||
test: {
|
||||
include: ['src/**/*.{test,spec}.{js,ts}']
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue