diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 00000000..3757b0e2 --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "$lib": ["src/lib"], + "$lib/*": ["src/lib/*"] + } + }, + "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"] +} diff --git a/package.json b/package.json index 9fde18c2..68d614f0 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,17 @@ { "name": "svelte-commerce", - "version": "2022.04.29", + "version": "2022.05.10", "scripts": { "dev": "svelte-kit dev --host", "gen": "graphql-codegen --require dotenv/config", - "build": "svelte-kit build", + "build": "cross-env NODE_OPTIONS=--max-old-space-size=7680 NODE_ENV=production svelte-kit build", + "copy": "node devops/copy", + "liveS": "node devops/live", + "prod": "run-s build copy", + "live": "run-s build copy liveS", + "start-node": "cross-env NODE_ENV=production node build/index.js", "package": "svelte-kit package", - "preview": "svelte-kit preview", + "start": "cross-env HOST=0.0.0.0 svelte-kit preview --host -p 2020", "quality": "npm run quality:eslint && npm run quality:prettier && npm run quality:ts", "quality:fix": "prettier --ignore-path .gitignore --write --plugin-search-dir=. . && npm run quality:eslint --fix", "quality:eslint": "eslint --ignore-path .gitignore .", @@ -16,65 +21,73 @@ "update": "npm exec -- npx ncu -u -p yarn" }, "devDependencies": { + "@fontsource/fira-mono": "^4.5.8", + "@playwright/test": "^1.20.0", + "@silintl/svelte-google-places-autocomplete": "1.1.6", "@sveltejs/adapter-auto": "next", - "@sveltejs/kit": "next", "@tailwindcss/forms": "^0.5.0", "@tailwindcss/line-clamp": "^0.3.1", "@tailwindcss/typography": "^0.5.2", + "@types/cookie": "0.4.1", + "@types/uuid": "8.3.4", "@typescript-eslint/eslint-plugin": "^5.21.0", "@typescript-eslint/parser": "^5.21.0", "autoprefixer": "^10.4.5", - "concurrently": "^7.1.0", - "cookie-universal": "^2.1.5", + "cl-editor": "^2.3.0", "cssnano": "^5.1.7", - "eslint": "^8.14.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-jest": "^26.1.5", - "eslint-plugin-node": "^11.0.0", - "eslint-plugin-prettier": "^4.0.0", - "eslint-plugin-promise": "^6.0.0", - "eslint-plugin-svelte3": "^3.4.1", - "graphql": "^16.4.0", - "graphql-tag": "^2.12.6", - "npm-run-all": "^4.1.5", + "npm-run-all": "4.1.5", "postcss": "^8.4.12", "postcss-cli": "^9.1.0", "postcss-load-config": "^3.1.4", "prettier": "^2.6.2", "prettier-plugin-svelte": "^2.7.0", - "shelljs": "^0.8.5", - "siema": "^1.5.1", - "ssh2shell": "^1.9.10", + "prettier-plugin-tailwindcss": "^0.1.10", + "quill": "^1.3.7", + "shelljs": "0.8.5", + "ssh2shell": "1.9.10", + "supabase": "0.5.0", "svelte": "^3.47.0", - "svelte-debugger": "^1.0.5", + "svelte-calendar": "3.1.6", + "svelte-carousel": "^1.0.17-rc4", + "svelte-geolocation": "^0.3.0", "svelte-hero-icons": "^4.1.1", - "svelte-preprocess": "^4.10.6", + "svelte-select": "4.4.7", "svelte-swipe": "^1.8.2", "svelte-timeago": "^0.1.2", "svelte-toasts": "^1.1.2", + "svelte-tree-select": "1.1.8", "tailwind-scrollbar": "^1.3.1", "tailwindcss": "^3.0.24", "tailwindcss-rtl": "^0.9.0", + "to-words": "^3.2.0", "tslib": "^2.4.0", "typescript": "^4.6.3" }, "type": "module", - "engines": { - "node": ">= 14.0.0" - }, "dependencies": { "@beyonk/async-script-loader": "^2.0.0", - "@fontsource/fira-mono": "^4.5.8", + "@beyonk/svelte-googlemaps": "3.2.0", "@kitql/all-in": "0.6.3", - "@lukeed/uuid": "^2.0.0", + "@supabase/supabase-js": "1.35.2", + "@sveltejs/adapter-node": "next", + "@sveltejs/kit": "1.0.0-next.324", "cookie": "^0.5.0", - "cross-env": "^7.0.3", + "cookie-universal": "^2.1.5", + "cross-env": "7.0.3", "dayjs": "^1.11.1", "graphql": "16.3.0", "graphql-tag": "^2.12.6", "hash-it": "^5.0.2", + "leaflet": "^1.8.0", "node-fetch": "^3.2.3", - "universal-cookie": "^4.0.4" + "simple-svelte-autocomplete": "^2.4.0", + "svelte-preprocess": "^4.10.6", + "universal-cookie": "^4.0.4", + "uuid": "8.3.2" + }, + "volta": { + "node": "17.4.0", + "yarn": "1.22.17", + "pnpm": "6.32.1" } } \ No newline at end of file diff --git a/robots.txt b/robots.txt new file mode 100644 index 00000000..e9e57dc4 --- /dev/null +++ b/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 96f70002..41b2b831 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -1,39 +1,31 @@ -import cookie from 'cookie' +import cookie, { serialize } from 'cookie' import type { Handle } from '@sveltejs/kit' import { DOMAIN } from '$lib/config' import { KQL_Init } from '$lib/graphql/_kitql/graphqlStores' +import { v4 as uuidv4 } from 'uuid' export const handle: Handle = async ({ event, resolve }) => { - let store, - headers, - serializedCookie, - settings = {}, - megamenu = [], - sid, - token + let store, cart, headers, serializedCookie, settings, megamenu, sid, token, domain, geo, me // me.gql and cart.gql can not be called here because of unabailability of session cookie. Hence there are executed at __layout.reset.svelte or $lib/Nav.svelte const request = event.request const cookies = cookie.parse(request.headers.get('cookie') || '') || {} - const domain = DOMAIN || request.headers.get('host') + event.locals['kitqlCartId'] = cookies.kitqlCartId || uuidv4() + domain = DOMAIN || request.headers.get('host') if (cookies.store) store = cookies.store && JSON.parse(cookies.store) - const INIT = ( - await KQL_Init.query({ - fetch, - variables: { domain } - }) - ).data - store = store = INIT?.storeOne - settings = INIT?.settings - megamenu = INIT?.megamenu - if (cookies.me) { - sid = cookies.sid - token = cookies.token + try { + const INIT = (await KQL_Init.query({ fetch, variables: { domain } })).data + store = store = INIT?.storeOne + settings = INIT?.settings + megamenu = INIT?.megamenu + if (cookies.me) { + me = cookies.me && JSON.parse(cookies.me) + sid = cookies.sid + token = cookies.token + } + } catch (e) {} + if (event.url.searchParams.has('_method')) { + event.method = event.url.searchParams.get('_method').toUpperCase() } - - const geo = cookies.geo && JSON.parse(cookies.geo) - // if (event.url.searchParams.has('_method')) { - // event.method = event.url.searchParams.get('_method').toUpperCase() - // } event.locals = { store, headers, @@ -44,16 +36,35 @@ export const handle: Handle = async ({ event, resolve }) => { token, domain, loginUrl: settings?.otpLogin ? '/auth/otp-login' : '/auth/login', - geo + kitqlCartId: event.locals['kitqlCartId'], + geo, + me } const response = await resolve(event) + if (!cookies.kitqlCartId) { + // if this is the first time the user has visited this app, + // set a cookie so that we recognise them when they return + response.headers.set( + 'Set-Cookie', + cookie.serialize('kitqlCartId', event.locals['kitqlCartId'], { + path: '/', + httpOnly: true + }) + ) + } + if (sid && !cookies.sid) { + response.headers.set( + 'Set-Cookie', + cookie.serialize('sid', sid.toString(), { path: '/', httpOnly: true }) + ) + } if (store) { - // const COOKIE_NAME = 'store' - // const secure = process.env.NODE_ENV === 'production' - // const maxAge = 7200 // (3600 seconds / hour) * 2 hours - // const sameSite = 'Strict' + const COOKIE_NAME = 'store' + const secure = process.env.NODE_ENV === 'production' + const maxAge = 7200 // (3600 seconds / hour) * 2 hours + const sameSite = 'Strict' const { id, email, address, phone, websiteName, websiteLegalName, stripePublishableKey } = store - const stringifiedData = + const stringifiedStoreData = JSON.stringify({ id, domain, @@ -62,25 +73,52 @@ export const handle: Handle = async ({ event, resolve }) => { email, websiteName, websiteLegalName, - stripePublishableKey + stripePublishableKey, + kitqlCartId: event.locals['kitqlCartId'] }) || '' - // const setCookieValue = `${COOKIE_NAME}=${stringifiedData}; Max-Age=${maxAge}; Path=/; ${ - // secure ? 'Secure;' : '' - // } HttpOnly; SameSite=${sameSite}` - serializedCookie = cookie.serialize('store', stringifiedData, { path: '/' }) + const setStoreCookieValue = `${COOKIE_NAME}=${stringifiedStoreData}; Max-Age=${maxAge}; Path=/; ${ + secure ? 'Secure;' : '' + } HttpOnly; SameSite=${sameSite}` // This line is important else footer will not populate + response.headers.set( + 'Set-Cookie', + cookie.serialize('store', stringifiedStoreData, { path: '/' }) + ) + } + if (geo && !cookies.geo) { + const COOKIE_NAME = 'geo' + const secure = process.env.NODE_ENV === 'production' + const maxAge = 3600 * 2 + const sameSite = 'Strict' + const stringifiedGeoData = JSON.stringify(geo) + const setGeoCookieValue = `${COOKIE_NAME}=${stringifiedGeoData}; Max-Age=${maxAge}; Path=/; ${ + secure ? 'Secure;' : '' + } HttpOnly; SameSite=${sameSite}` // This line is important else footer will not populate + response.headers.set('Set-Cookie', cookie.serialize('geo', stringifiedGeoData, { path: '/' })) } - response.headers.set('Set-Cookie', serializedCookie) return response } export const getSession = ({ locals }) => { - const { token, geo, store, loginUrl, domain, sid, categories, settings, megamenu } = locals - return { + const { + token, + geo, loginUrl, + domain, + sid, + kitqlCartId, + categories, + settings, + store, megamenu, + me + } = locals + return { + me, + kitqlCartId, + loginUrl, + store, settings, domain, - store, geo, sid, token, diff --git a/src/lib/Nav.svelte b/src/lib/Nav.svelte index 1b0b108d..933551b8 100644 --- a/src/lib/Nav.svelte +++ b/src/lib/Nav.svelte @@ -22,31 +22,49 @@ import { Categories } from './graphql/_kitql/graphqlTypes' import { KQL_Cart, KQL_Init, KQL_Me } from './graphql/_kitql/graphqlStores' import { signOut } from './services' import { onMount } from 'svelte' -import { store } from './../util' +import { toast } from './util' onMount(async () => { await KQL_Me.query({}) await KQL_Cart.query({ settings: { policy: 'network-only' } }) }) -export let section +export let q, me, store async function handleSignout() { - await signOut() + try { + await signOut() + $session.me = null + $session.token = null + $session.sid = null + toast('Signed Out...', 'success') + goto('/auth/login') + } catch (e) { + toast(e, 'error') + } finally { + } } + +onMount(async () => { + try { + await KQL_Cart.query({ variables: { store: store?.id }, settings: { cacheMs: 0 } }) + } catch (e) { + } finally { + } +}) -