This commit is contained in:
matthieu42morin 2024-07-03 10:10:15 +02:00
parent 870a6f2016
commit f833c9c206
16 changed files with 224 additions and 202 deletions

View File

@ -1,6 +1,7 @@
# 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`
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 either in `/static` or are on [Cloudinary](#cloudinary-with-some-scripting) and are printed at multiple places in multiple formats.
The texts are a work of mom, me and Google's Gemini( it's stupid for most complex tasks, but it's good at rewriting text and adding marketing emotions and so on, especially good at Czech unlike the other models)
## Webapp stack
@ -33,9 +34,17 @@ Everything has a schema (e.g. `./sluzby/schema#`) or a type or a generatable tem
This implementation offers an option to create or edit json files a [JSON editor](https://github.com/json-editor/json-editor) according to a schema to help write new services in a GUI.
The json data is validated by [ajv](https://ajv.js.org) with my script in `./tests/ValidateServices.js/` to match the type Service in `$lib/types/service.d.ts`
### Cloudinary with some scripting
I automated checking for images in the content folder and uploading them to Cloudinary.
I also have a script that generates a json file with all the images in the content folder and uploads them to Cloudinary.
This json file is then used to get images as simple keys.
## Features, Components and parts
A list of mostly
### SEO + Opengraph + frontmatter
my own implementation
### [LibreMaps](https://svelte-maplibre.vercel.app/)
@ -81,7 +90,7 @@ Selfhosting this is the only way. I used ansible and terraform to get this thing
### Email obfuscation
Spammers are hopefully not so smart. I sveltyfied a [SVG email obfuscation method (because JS bad) from https://rouninmedia.github.io/protecting-your-email-address-via-svg-instead-of-js/](https://rouninmedia.github.io/protecting-your-email-address-via-svg-instead-of-js/)
Spammers are hopefully not so smart. I sveltyfied a [SVG email obfuscation method (because JS bad) from https://rouninmedia.github.io/protecting-your-email-address-via-svg-instead-of-js/](https://rouninmedia.github.io/protecting-your-email-address-via-svg-instead-of-js/).
### OpenGraph

View File

@ -9,7 +9,7 @@
"validate": "pnpm run validate:services && validate:images",
"dev": "pnpm run validate && vite dev --mode development",
"build": "pnpm run validate && vite build",
"build-dev": "pnpm run validate && vite build --mode development",
"build-dev": "pnpm run vite build --mode development",
"preview": "vite preview",
"test": "pnpm run test:integration && npm run test:unit",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
@ -26,7 +26,11 @@
"dependencies": {
"@fortawesome/fontawesome-free": "^6.5.1",
"@sentry/sveltekit": "^7.107.0",
"@sveltejs/adapter-cloudflare": "^4.4.0",
"@sveltejs/adapter-netlify": "^4.2.0",
"@sveltejs/adapter-node": "^5.0.1",
"@sveltejs/adapter-static": "^3.0.1",
"@sveltejs/adapter-vercel": "^5.3.0",
"@sveltejs/vite-plugin-svelte": "^3.0.2",
"ajv": "^8.12.0"
},

View File

@ -8,7 +8,7 @@
"title": "Lifting řas booster (botox)",
"description": "Diagnostika pleti, odlíčení tonizace",
"id": "lifting-ras-booster",
"image": "",
"image": "1,2,zola",
"price": 500,
"duration": 60
},

View File

@ -5,23 +5,23 @@
"image": "https://images.unsplash.com/xyz",
"services": [
{
"title": "Obočí Pudrové, Ombré",
"description": "Diagnostika pleti, odlíčení tonizace",
"title": "Obočí Pudrové",
"description": "Správně upravené obočí dokáže obličej rozjasnit i omladit. Pokud je vaše obočí příliš světlé / řídké nebo vás prostě jen zdržuje každodenní úprava do požadovaného tvaru, nabízím vám velmi oblíbenou metodu permanentního make-upu pudrové obočí která je naprosto skvělým řešením!",
"id": "oboci",
"image": "",
"price": 3000,
"price": 3500,
"duration": 150
},
{
"title": "Horní linky - meziřasové přirozené",
"title": "Meziřasové Linky",
"description": "Diagnostika pleti, odlíčení tonizace",
"id": "linky",
"image": "",
"price": 2000,
"price": 2500,
"duration": 120
},
{
"title": "Klasické linky - s ocáskem",
"title": "Klasické linky",
"description": "Diagnostika pleti, odlíčení tonizace",
"id": "classic-linky",
"image": "",
@ -29,15 +29,7 @@
"duration": 150
},
{
"title": "Klasické linky - s ocáskem + spodní linky",
"description": "Diagnostika pleti, odlíčení tonizace",
"id": "classic-linky+spodni",
"image": "",
"price": 3500,
"duration": 150
},
{
"title": "Rty - kontura",
"title": "Rty",
"description": "Diagnostika pleti, odlíčení tonizace",
"id": "rty",
"image": "",
@ -45,24 +37,8 @@
"duration": 120
},
{
"title": "3D Rty (kontura a stínování), Full Lips (plné rty)",
"description": "Diagnostika pleti, odlíčení tonizace",
"id": "3d-rty",
"image": "",
"price": 3500,
"duration": 150
},
{
"title": "Aquarelle Lips (přirodní stínování, bez kontury)",
"description": "Diagnostika pleti, odlíčení tonizace",
"id": "aquarelle",
"image": "",
"price": 3000,
"duration": 120
},
{
"title": "První korekce po aplikaci pmu max. do 3 měsíců",
"description": "Diagnostika pleti, odlíčení tonizace",
"title": "Korekce",
"description": "Pro docílení perfektního vzhledu vašeho permanentního makeupu jsou potřeba dvě návštěvy. Ideální načasování pro druhou návštěvu je 1-3 měsíce od prvního zákroku.",
"id": "korekce",
"image": "",
"price": 1000,

View File

@ -7,7 +7,7 @@
{
"title": "Ošetření horních končetin",
"description": "Diagnostika pleti, odlíčení tonizace",
"id": "vakuslim-48-zestihlujici-procedura-horni-koncetiny",
"id": "horni-koncetiny",
"image": "",
"price": 600,
"duration": 120
@ -15,7 +15,7 @@
{
"title": "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",
"id": "spodni-cast-tela",
"image": "",
"price": 800,
"duration": 120
@ -23,15 +23,15 @@
{
"title": "1 ošetření komplet horní-dolní části",
"description": "Diagnostika pleti, odlíčení tonizace",
"id": "vakuslim-48-zestihlujici-procedura-komplet-horni-dolni-cast",
"id": "komplet-horni-dolni-cast",
"image": "",
"price": 1200,
"duration": 120
},
{
"title": "6 ošetření předplatné kompet",
"title": "6 ošetření předplatné komplet",
"description": "Diagnostika pleti, odlíčení tonizace",
"id": "vakuslim-48-zestihlujici-procedura-6-o-setreni-predplatne-kompet",
"id": "6-o-setreni-predplatne-kompet",
"image": "",
"price": 6600,
"duration": 120
@ -39,7 +39,7 @@
{
"title": "12 ošetření předplatné komplet",
"description": "Diagnostika pleti, odlíčení tonizace",
"id": "vakuslim-48-zestihlujici-procedura-12-o-setreni-predplatne-komplet",
"id": "12-o-setreni-predplatne-komplet",
"image": "",
"price": 11000,
"duration": 120

View File

@ -0,0 +1,35 @@
# Svatebni Liceni
Vytvořím Vám jemné, stylové líčení na svatbu a nemusíte se bát ani slziček, protože voděodolný make-up vydrží naprosto vše (a na pohled bude stále decentní), ale přesto dostatečně výrazný jak na fotkách, tak na videu. Cílem mé práce je, aby pleť vypadala přirozeně a svěže po celý svatební den.
Abyste se ve svůj den D cítila krásně, je třeba začít se správnou péčí o pleť minimálně 3 měsíce před svatbou. Pokud péči o pleť nebudete věnovat pozornost, ani dokonalý svatební make-up vám nezaručí vysněný vzhled, ba naopak, může i uškodit a vypadat nepřirozeně.
## KROKY PŘÍPRAVY
Nevěsta by měla absolvovat minimálně jednu zkoušku, která by měla proběhnout nejpozději týden před obřadem.
Nejdříve VÁM zhodnotím typ pleti, tvar obličeje, tvar očí, úst a následně doporučím a vyzkoušíme make-up přímo na míru. Někomu sluší spíš takzvaný „nude“ a někomu zase naopak výraznější líčení. Díky této zkoušce klientka bude vědět v jakém make-upu se cítí nejlépe a doladí se všechny detaily.
Milé budoucí nevěsty, přineste si s sebou na fotce také svatební šaty a kytici. Je totiž důležité, aby make-up ladil s celkovým stylem a barvou svatby.
## PRŮBĚH V DEN D (SVATEBNÍ DEN)
Ve svatební den nejprve pleť správně připravíme pomocí kosmetických přípravků (vyčištění, hydratace, tonikum, hydratační sérum, pleťový krém) dále make-upu, zakryje drobné nedokonalosti korektorem, přepudruje se pleť a pomocí konturování podtrhne Vaší krásu. Dále pomocí stínů zvýrazní oči a pokud má nevěsta zájem, lze nalepit i řasy. Na závěr se vybere vhodný odstín rtěnky, make-up se zafixuje a vy si můžete jít naplno užít Váš den.
## Cenik svatebního líčení
Zkouška svatebního líčení trvá přibližně 60-90 minut a stojí 990 Kč.
Zkouška účesu 300,-
Svatební den líčení 1500,-
Svatební den účes 400,-
Možná domluva prodloužení a zahuštění vlasů metodou keratin (pásky) cena se odvíjí od hustoty (používám pásky 100% human hair, levnější varianta 60-80% mix lidské+sintet. Potřebná domluva předem.
Ceník human hair 100% 20pásků 3500,-
Ceník human+syntetika 20pásků 1500,-
Ceník sundání pramenů do 30min 350,-

View File

@ -0,0 +1,17 @@
# VEČERNÍ + PLESOVÉ LÍČENÍ
Zahrnuje: báze, korektory, velká modelace obličeje, make-up, pudr, stíny, linky, řasy, obočí, líčka, rozjasňovače, fixátor a aplikace umelých řas (trvanlivost na noc), popř. účes.
60-90min. 990,-
120min. líčení + účes 1500,-
## Prodloužení vlasů
Možná domluva prodloužení a zahuštění vlasů metodou keratin (pásky) cena se odvíjí od hustoty (používám pásky 100% human hair, levnější varianta 60-80% mix lidské+syntet. Potřebná domluva předem.
Ceník human hair 100% 20pásků 4500,-
Ceník human+syntetika 20pásků 1500,-
Ceník sundání pramenů do 30min 350,-

View File

@ -0,0 +1,24 @@
{
"title": "Vizážistika",
"description": "Nabízím Vám líčení a účes současně, ušetřete svůj čas o svatebním dni :-), večerní a plesové líčení, základní denní líčení.",
"id": "vizazistika",
"image": "https://images.unsplash.com/xyz",
"services": [
{
"title": "Svatební Líčení",
"description": "Vytvořím Vám jemné, stylové líčení na svatbu a nemusíte se bát ani slziček, protože voděodolný make-up vydrží naprosto vše (a na pohled bude stále decentní), ale přesto dostatečně výrazný jak na fotkách, tak na videu. Cílem mé práce je, aby pleť vypadala přirozeně a svěže po celý svatební den.",
"id": "svatebni",
"image": "oboci1.png",
"price": 3500,
"duration": 150
},
{
"title": "Večerní & Plesové Líčení",
"description": "Diagnostika pleti, odlíčení tonizace",
"id": "vecerni",
"image": "",
"price": 2500,
"duration": 120
}
]
}

View File

@ -0,0 +1,5 @@
# VIZÁŽISTIKA
Nabízím Vám líčení a účes současně, ušetřete svůj čas o svatebním dni :-), večerní a plesové líčení, základní denní líčení.
Práci vizážistky se věnuji po absolvování profesionální školy Make- up institute. Mám osobitý přístup a Vaše spokojenost mě motivuje k lepším výsledkům. Kromě služeb vizážistiky si Vás mohu připravit předem i kosmetickým ošetřením, do salonu mi dojíždí i manikérka. Spolupracuji s profesionální fotografkou, možná domluva focení.

View File

@ -1,3 +1,23 @@
<script lang="ts">
</script>
<section>
<!-- Úvod -->
</section>
<section>
<!-- Foto -->
</section>
<section>
<!-- Certifikace -->
</section>
<section>
<!-- Foto -->
</section>
<section>
<!-- -->
</section>

View File

@ -1,97 +0,0 @@
<!--
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>

View File

@ -1,6 +0,0 @@
// 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");
};

View File

@ -10,3 +10,4 @@
<SEO seoData={data}/>
<!-- Your service content goes here -->

View File

@ -1,42 +1,69 @@
import adapter from '@sveltejs/adapter-node';
import preprocess from 'svelte-preprocess';
// svelte adapter
// config extensions
import { mdsvex } from 'mdsvex';
import mdsvexConfig from './mdsvex.config.js';
import adapterNode from '@sveltejs/adapter-node'
import adapterVercel from '@sveltejs/adapter-vercel'
import adapterNetlify from '@sveltejs/adapter-netlify'
import adapterCloudflare from '@sveltejs/adapter-cloudflare'
import adapterStatic from '@sveltejs/adapter-static'
// svelte preprocessor
import { mdsvex } from 'mdsvex'
import mdsvexConfig from './mdsvex.config.js'
// import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
import preprocess from 'svelte-preprocess'
/** @type {import('@sveltejs/kit').Config} */
const config = {
extensions: ['.svelte', ...(mdsvexConfig.extensions || [])],
// Consult https://github.com/sveltejs/svelte-preprocess
// for more information about preprocessors
preprocess: [
preprocess({
postcss: true
}),
mdsvex(mdsvexConfig)
],
kit: {
adapter: adapter({
out: 'build',
precompress: false
}),
function getAdapter() {
if (Object.keys(process.env).some(key => key.includes('VERCEL'))) {
return adapterVercel()
} else if (Object.keys(process.env).some(key => key.includes('NETLIFY'))) {
return adapterNetlify()
} else if (Object.keys(process.env).some(key => key.includes('CF_PAGES'))) {
return adapterCloudflare()
} else {
return process.env.ADAPTER === 'node'
? adapterNode({ out: 'build' })
: adapterStatic({
pages: 'build',
assets: 'build',
fallback: undefined
})
}
}
// Aliases need tsconfig explicit inclusion
alias: {
$lib: './src/lib',
$root: './',
$src: './src',
$routes: './src/routes',
$content: './src/content'
},
env: {
publicPrefix: "PUBLIC_",
},
/** @type {import("@svletejs/kit".Config)} */
export default {
extensions: ['.svelte', ...(mdsvexConfig.extensions || [])],
preprocess: [preprocess({ postcss: true }), mdsvex(mdsvexConfig) /*, vitePreprocess()*/],
vitePlugin: {
inspector: true
},
kit: {
adapter: getAdapter(),
alias: {
$lib: './src/lib',
$root: './',
$src: './src',
$routes: './src/routes',
$content: './content'
},
csrf: {
checkOrigin: process.env.NODE_ENV === 'development' ? false : true
},
prerender: {
crawl: true,
handleMissingId: 'warn',
handleHttpError: ({ status, path, referrer, referenceType, message }) => {
// Handle blog trying to prerender relative links that it can't
if (
(status == 404 && path.startsWith('/blog')) ||
path.startsWith('/projects') ||
(path.startsWith('/') && referenceType == 'linked')
) {
console.warn(`PRERENDER ignored route ${path}`)
return
}
// https://kit.svelte.dev/docs/configuration#alias
},
};
export default config;
throw new Error(`${status} ${path} from ${referrer}, ~~~~~~~~~ message: ${message}~~~~~~~~~`)
}
}
}
}

View File

@ -11,21 +11,24 @@ cloudinary.v2.config({
async function validateImages() {
const invalidImages = [];
for (const publicId in imagesData) {
try {
// Check if the image exists on Cloudinary
await cloudinary.v2.api.resource(publicId);
} catch (error) {
invalidImages.push(publicId);
try {
for (const publicId in imagesData) {
try {
// Check if the image exists on Cloudinary
await cloudinary.v2.api.resource(publicId);
} catch (error) {
invalidImages.push(publicId);
}
}
}
if (invalidImages.length > 0) {
console.error(`The following images are missing or invalid on Cloudinary: ${invalidImages.join(', ')}`);
process.exit(1);
} else {
console.log('All images are valid on Cloudinary.');
if (invalidImages.length > 0) {
console.error(`The following images are missing or invalid on Cloudinary: ${invalidImages.join(', ')}`);
process.exit(1);
} else {
console.log('All images are valid on Cloudinary.');
}
} catch (error) {
console.error(error);
}
}

View File

@ -8,6 +8,9 @@ export default defineConfig({
server: {
host: 'localhost',
port: 5174,
fs: {
allow: ['..'],
}
},
envPrefix: "PUBLIC_",
plugins: [sentrySvelteKit({
@ -34,10 +37,11 @@ export default defineConfig({
},
resolve: {
alias: {
$lib: path.resolve(__dirname, 'src', 'lib'),
$root: path.resolve(__dirname),
$src: path.resolve(__dirname, 'src'),
$routes: path.resolve(__dirname, 'src', 'routes')
$lib: path.resolve('.', 'src/lib'),
$root: path.resolve('.'),
$src: path.resolve('.', 'src'),
$routes: path.resolve('.', 'src/routes'),
$content: path.resolve('.', 'src/content'),
}
}
});