diff --git a/src/services/cookie.ts b/src/services/cookie.ts new file mode 100644 index 00000000..cb051bd9 --- /dev/null +++ b/src/services/cookie.ts @@ -0,0 +1,28 @@ +import Cookies from 'js-cookie'; + +type CookieListener = (name: string, value: string | undefined) => void; + +class Service { + listeners: CookieListener[] = []; + + set = (name: string, value: string, options: Cookies.CookieAttributes = { expires: 30 }) => { + Cookies.set(name, value, options); + this.listeners.forEach((listener) => listener(name, value)); + }; + + get = (name: string) => { + return Cookies.get(name); + }; + + listen = (listener: CookieListener) => { + if (!this.listeners.includes(listener)) this.listeners.push(listener); + }; + + dismiss = (listener: CookieListener) => { + this.listeners = this.listeners.filter((l) => l !== listener); + }; +} + +const CookieService = new Service(); + +export default CookieService; diff --git a/src/services/local-storage.ts b/src/services/local-storage.ts new file mode 100644 index 00000000..5603fd1e --- /dev/null +++ b/src/services/local-storage.ts @@ -0,0 +1,47 @@ +type LocalStorageListener = (name: string, value: string | undefined) => void; + +const KEY = 'NEXTJS-BOILERPLATE'; + +class Service { + listeners: LocalStorageListener[] = []; + + set = (name: string, value: string): boolean => { + try { + if (value !== undefined) { + const serializedData = localStorage.getItem(KEY); + if (serializedData === null) { + localStorage.setItem(KEY, JSON.stringify({ [name]: value })); + } else { + localStorage.setItem(KEY, JSON.stringify({ ...JSON.parse(serializedData), [name]: value })); + } + } + this.listeners.forEach((listener) => listener(name, value)); + return true; + } catch (e) { + // no local storage (ssr or incognito mode) + return false; + } + }; + + get = (name: string): string | null => { + try { + const serializedData = localStorage.getItem(KEY); + if (!serializedData) return null; + return JSON.parse(serializedData)[name] || null; + } catch (e) { + // no local storage (ssr or incognito mode) + return null; + } + }; + + listen = (listener: LocalStorageListener) => { + if (!this.listeners.includes(listener)) this.listeners.push(listener); + }; + + dismiss = (listener: LocalStorageListener) => { + this.listeners = this.listeners.filter((l) => l !== listener); + }; +} +const LocalStorageService = new Service(); + +export default LocalStorageService; diff --git a/src/services/lock-body-scroll.ts b/src/services/lock-body-scroll.ts index 6c9b6496..557fe36a 100644 --- a/src/services/lock-body-scroll.ts +++ b/src/services/lock-body-scroll.ts @@ -6,7 +6,7 @@ import scrollPage from '@/utils/scroll-page'; /** * Lock and unlock body scroll with page position restoration */ -class LockBodyScroll { +class Service { scrollPosY = 0; isLocked = false; @@ -33,4 +33,6 @@ class LockBodyScroll { }; } -export default new LockBodyScroll(); +const LockBodyScrollService = new Service(); + +export default LockBodyScrollService; diff --git a/src/services/orientation.ts b/src/services/orientation.ts index 9c1a67f0..91b057dc 100644 --- a/src/services/orientation.ts +++ b/src/services/orientation.ts @@ -1,6 +1,6 @@ type OrientationListener = (e?: Event) => void; -class OrientationService { +class Service { listeners: OrientationListener[] = []; onOrientation = (e: Event) => { @@ -22,4 +22,6 @@ class OrientationService { }; } -export default new OrientationService(); +const OrientationService = new Service(); + +export default OrientationService; diff --git a/src/services/pointer-move.ts b/src/services/pointer-move.ts index 0d05b350..3b67247e 100644 --- a/src/services/pointer-move.ts +++ b/src/services/pointer-move.ts @@ -2,7 +2,7 @@ import { device } from '@jam3/detect'; type PointerMoveListener = (x?: number, y?: number, e?: MouseEvent | TouchEvent) => void; -class PointerMoveService { +class Service { listeners: PointerMoveListener[] = []; onMove = (e: MouseEvent | TouchEvent) => { @@ -26,4 +26,6 @@ class PointerMoveService { }; } -export default new PointerMoveService(); +const PointerMoveService = new Service(); + +export default PointerMoveService; diff --git a/src/services/raf.ts b/src/services/raf.ts index 19eb68b3..1362d928 100644 --- a/src/services/raf.ts +++ b/src/services/raf.ts @@ -1,6 +1,6 @@ -type RequestAnimationFrameListener = (delta?: number) => void; +type RequestAnimationFrameListener = ((delta?: number) => void) | ((delta: number) => void); -class RequestAnimationFrameService { +class Service { listeners: RequestAnimationFrameListener[] = []; frameId = 0; elapsed = 0; @@ -30,4 +30,6 @@ class RequestAnimationFrameService { }; } -export default new RequestAnimationFrameService(); +const RafService = new Service(); + +export default RafService; diff --git a/src/services/resize.ts b/src/services/resize.ts index 9da80ce9..5fcf4491 100644 --- a/src/services/resize.ts +++ b/src/services/resize.ts @@ -2,21 +2,28 @@ import { device } from '@jam3/detect'; type ResizeListener = (e?: Event | UIEvent) => void; -class ResizeService { +let timeout: NodeJS.Timeout; +class Service { + debounceTime = 10; // in ms listeners: ResizeListener[] = []; onResize = (e: Event | UIEvent) => { - setTimeout( - () => { - this.listeners.forEach((listener) => listener(e)); - }, - device.mobile ? 500 : 0 // some mobile browsers only update window dimensions when the rotate animation finishes - ); + clearTimeout(timeout); + + timeout = setTimeout(() => { + this.listeners.forEach((listener) => listener(e)); + if (device.mobile) { + timeout = setTimeout(() => { + this.listeners.forEach((listener) => listener(e)); + }, 500); // some mobile browsers only update window dimensions when the rotate animation finishes + } + }, this.debounceTime); }; listen = (listener: ResizeListener) => { if (!this.listeners.length) { - window.addEventListener(device.mobile ? 'orientationchange' : 'resize', this.onResize); + window.addEventListener('resize', this.onResize); + if (device.mobile) window.addEventListener('orientationchange', this.onResize); } if (!this.listeners.includes(listener)) this.listeners.push(listener); }; @@ -24,9 +31,12 @@ class ResizeService { dismiss = (listener: ResizeListener) => { this.listeners = this.listeners.filter((l) => l !== listener); if (!this.listeners.length) { - window.removeEventListener(device.mobile ? 'orientationchange' : 'resize', this.onResize); + window.removeEventListener('resize', this.onResize); + if (device.mobile) window.removeEventListener('orientationchange', this.onResize); } }; } -export default new ResizeService(); +const ResizeService = new Service(); + +export default ResizeService; diff --git a/src/services/scroll.ts b/src/services/scroll.ts index 5a8cc850..d8167ee1 100644 --- a/src/services/scroll.ts +++ b/src/services/scroll.ts @@ -1,6 +1,6 @@ type ScrollListener = (e?: Event) => void; -class ScrollService { +class Service { listeners: ScrollListener[] = []; onScroll = (e: Event) => { @@ -22,4 +22,6 @@ class ScrollService { }; } -export default new ScrollService(); +const ScrollService = new Service(); + +export default ScrollService; diff --git a/src/services/visibility.ts b/src/services/visibility.ts index 9c87ff3a..7afb5110 100644 --- a/src/services/visibility.ts +++ b/src/services/visibility.ts @@ -1,6 +1,6 @@ -type VisibilityListener = (e?: Event) => void; +type VisibilityListener = ((e: Event) => void) | ((e?: Event) => void); -class VisibilityService { +class Service { listeners: VisibilityListener[] = []; onVisibility = (e: Event) => { @@ -22,4 +22,6 @@ class VisibilityService { }; } -export default new VisibilityService(); +const VisibilityService = new Service(); + +export default VisibilityService;