diff --git a/bun.lockb b/bun.lockb index c523d85..c7dfb49 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 5683b2f..4b85dbe 100644 --- a/package.json +++ b/package.json @@ -12,38 +12,38 @@ "dependencies": { "@astrojs/check": "^0.4.1", "@astrojs/node": "^8.3.2", - "@astrojs/react": "^3.0.9", + "@astrojs/react": "^3.6.0", "@astrojs/tailwind": "^5.1.0", - "@astrojs/vercel": "^7.3.1", - "@radix-ui/react-checkbox": "^1.0.4", + "@astrojs/vercel": "^7.7.2", + "@radix-ui/react-checkbox": "^1.1.1", "@radix-ui/react-icons": "^1.3.0", - "@radix-ui/react-label": "^2.0.2", - "@radix-ui/react-separator": "^1.0.3", - "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-separator": "^1.1.0", + "@radix-ui/react-slot": "^1.1.0", "@types/leaflet": "^1.9.12", - "@types/react": "^18.2.47", - "@types/react-dom": "^18.2.18", - "astro": "^4.1.2", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "astro": "^4.11.6", "class-variance-authority": "^0.7.0", - "clsx": "^2.1.0", - "dayjs": "^1.11.10", + "clsx": "^2.1.1", + "dayjs": "^1.11.11", "htmx.org": "^1.9.12", - "kysely": "^0.26.0", + "kysely": "^0.26.3", "leaflet": "^1.9.4", "lit": "^3.1.4", "lucide-react": "^0.309.0", - "nanoid": "^5.0.4", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "tailwind-merge": "^2.2.0", - "tailwindcss": "^3.4.1", + "nanoid": "^5.0.7", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "tailwind-merge": "^2.4.0", + "tailwindcss": "^3.4.6", "tailwindcss-animate": "^1.0.7", "toastify-js": "^1.12.0", - "typescript": "^5.3.3" + "typescript": "^5.5.3" }, "devDependencies": { - "@types/bun": "^1.0.5", - "@types/google.maps": "^3.54.10", + "@types/bun": "^1.1.6", + "@types/google.maps": "^3.55.11", "@types/toastify-js": "^1.12.3" } } diff --git a/src/components/LockedContent/templates.ts b/src/components/LockedContent/templates.ts new file mode 100644 index 0000000..a824f10 --- /dev/null +++ b/src/components/LockedContent/templates.ts @@ -0,0 +1,80 @@ +import { html, type TemplateResult } from "lit"; + +// This template is shown when user hasn't give geolocation permission yet +// When user click the button user is asked for geolocation permission +function permissionButtonTemplate(onClickHandler: () => void) { + return html` +
+ +
+
+

+ Ne kadar yaklaştığını görmek için aşağıdaki butona bas. +

+ +
+
+
+ `; +} + +// This template is shown when user has given permission but has not arrived yet +function lockedButtonTemplate(icon: TemplateResult<1>, proximityText: string) { + return html`
+ +
+
+

+ İçeriği görmek için konuma gitmelisin! Kalan mesafe: ${proximityText} +

+
+
+
`; +} + +// This template is shown when user has arrived to the target location +// When user click the button counter at the bottom of the page is incremented +// and image is revealed +function unlockedButtonTemplate( + icon: TemplateResult<1>, + onClickHandler: () => void +) { + return html`
+ + +
+
+

İçeriği görmek için butona bas!

+
+
+
`; +} + +export { + lockedButtonTemplate, + unlockedButtonTemplate, + permissionButtonTemplate, +}; diff --git a/src/components/locked-content-lit.ts b/src/components/locked-content-lit.ts index 8044ee3..5a85317 100644 --- a/src/components/locked-content-lit.ts +++ b/src/components/locked-content-lit.ts @@ -22,14 +22,14 @@ export class LockedContent extends LitElement { unsafeCSS(lockedContentStyles), ]; - // Static properties, no accessor attribute disables detecting changes - // as these are readonly attriubtes there is no need to attach setters + // Components properties/attributes, no accessor attribute disables detecting + // changes as these are readonly attriubtes there is no need to attach setters @property({ noAccessor: true }) readonly imageId?: string; @property({ noAccessor: true }) readonly imageURL?: string; @property({ type: Object, noAccessor: true }) readonly targetPosition?: LatLngTuple; - // Reactive state + // Reactive states, template is rendered according to this states @state() protected _hasGeolocationPermission = false; @state() @@ -41,8 +41,37 @@ export class LockedContent extends LitElement { @state() protected _watchId?: number; + // Locked lock icon + lockSVG = html` + + `; + + // Unlocked lock icon + unlockSVG = html` + + + + `; + // This callback will be fired when geolocation info is available successCallback(position: GeolocationPosition) { + // Set hasGeolocationPermission state true to change the template if (!this._hasGeolocationPermission) this._hasGeolocationPermission = true; const pos = { @@ -50,21 +79,29 @@ export class LockedContent extends LitElement { lng: position.coords.longitude, }; + // targetPosition attribute must be set for geolocation feature to work if (!this.targetPosition) return; + // Get target position in latitudes and longitudes const targetLatLng = L.latLng(this.targetPosition); + // Get current position in latitudes and longitudes const currentLatLng = L.latLng(pos); + // Calculate the distance between target and current position in meters const betweenMeters = currentLatLng.distanceTo(targetLatLng); + // Update the proximity text according to the distance remaining if (betweenMeters > 1000) { this._targetProximityText = `${(betweenMeters / 1000).toFixed()} KM`; } else if (betweenMeters > 100) { this._targetProximityText = `${betweenMeters.toFixed(0)} M`; } else { + // If target is close less then 100 meters user has arrived to target location if (this._watchId) { + // Stop watching location navigator.geolocation.clearWatch(this._watchId); + // Update state to reveal the image this._arrived = true; } } @@ -73,6 +110,7 @@ export class LockedContent extends LitElement { // This callback will be fired on geolocation error errorCallback(err: GeolocationPositionError) { let errorMessage; + // Show toast accoring to the error state switch (err.code) { case GeolocationPositionError.PERMISSION_DENIED: errorMessage = @@ -95,18 +133,20 @@ export class LockedContent extends LitElement { Toastify({ text: errorMessage, duration: 3000, - gravity: "top", // `top` or `bottom` - position: "center", // `left`, `center` or `right` - stopOnFocus: true, // Prevents dismissing of toast on hover + gravity: "top", + position: "center", + stopOnFocus: true, style: { background: "black", borderRadius: "6px", margin: "16px", }, - onClick: function () {}, // Callback after click + onClick: function () {}, }).showToast(); } + // This template is shown when user hasn't give geolocation permission yet + // When user click the button user is asked for geolocation permission permissionButtonTemplate() { return html`
@@ -135,13 +175,15 @@ export class LockedContent extends LitElement { `; } + // This template is shown when user has given permission but has not arrived yet lockedButtonTemplate() { return html`
@@ -154,6 +196,9 @@ export class LockedContent extends LitElement {
`; } + // This template is shown when user has arrived to the target location + // When user click the button counter at the bottom of the page is incremented + // and image is revealed unlockedButtonTemplate() { return html`
@@ -175,8 +221,9 @@ export class LockedContent extends LitElement {
`; } + // Start watching user location, if user has not given permission yet + // this will ask the user for permission and update the watch id private _startWatchingLocation() { - // start geolocation services const id = navigator.geolocation.watchPosition( this.successCallback.bind(this), this.errorCallback.bind(this), @@ -186,6 +233,8 @@ export class LockedContent extends LitElement { this._watchId = id; } + // This counter is shown at the bottom of the page and incremented + // each time "show content" button is clicked private async _incrementUnlockCounter(id: string | undefined) { if (id) { fetch(`http://localhost:3000/api/location/increment/${id}`, { @@ -197,6 +246,8 @@ export class LockedContent extends LitElement { connectedCallback(): void { super.connectedCallback(); + // Check geolocation permission, if user has given permission before + // start watching user location navigator.permissions .query({ name: "geolocation" }) .then((permissionStatus) => { @@ -217,6 +268,11 @@ export class LockedContent extends LitElement { render() { let buttonTemplate; + // Determine which template to show, there are 3 states: + // 1 - No geolocation permission given + // 2 - Permission given but has no arrived to target position yet + // 3 - Arrived to target position + // 4 - User did not give geolocation permission if (this._arrived) { buttonTemplate = this.unlockedButtonTemplate.bind(this); } else if (this._hasGeolocationPermission) { @@ -233,7 +289,7 @@ export class LockedContent extends LitElement { ${this._unlocked ? nothing : buttonTemplate()}