refactor: remove stale code
This commit is contained in:
parent
5097f70d90
commit
9e798f1b15
|
@ -20,20 +20,15 @@
|
|||
"@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.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"astro": "^4.11.6",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.11",
|
||||
"htmx.org": "^1.9.12",
|
||||
"hyperscript.org": "^0.9.12",
|
||||
"kysely": "^0.26.3",
|
||||
"leaflet": "^1.9.4",
|
||||
"lit": "^3.1.4",
|
||||
"lucide-react": "^0.309.0",
|
||||
"nanoid": "^5.0.7",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"tailwind-merge": "^2.4.0",
|
||||
|
@ -43,6 +38,9 @@
|
|||
"typescript": "^5.5.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/leaflet": "^1.9.12",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/bun": "^1.1.6",
|
||||
"@types/google.maps": "^3.55.11",
|
||||
"@types/toastify-js": "^1.12.3"
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Before Width: | Height: | Size: 320 KiB |
|
@ -1,59 +0,0 @@
|
|||
import L, { type LatLngTuple } from "leaflet";
|
||||
|
||||
const GoToTargetControl = L.Control.extend({
|
||||
_targetLocation: null as LatLngTuple | null,
|
||||
setTargetLocation: function (latlng: LatLngTuple) {
|
||||
this._targetLocation = latlng;
|
||||
},
|
||||
onAdd: function (map: L.Map) {
|
||||
const locationButton = document.createElement("button");
|
||||
|
||||
locationButton.id = "go-to-target-control-button";
|
||||
|
||||
locationButton.textContent = "Hedefe Git";
|
||||
|
||||
locationButton.classList.add("custom-map-control-button");
|
||||
|
||||
L.DomEvent.on(locationButton, "click", () => {
|
||||
if (!this._targetLocation) return;
|
||||
map.setView(this._targetLocation, 18);
|
||||
});
|
||||
|
||||
return locationButton;
|
||||
},
|
||||
});
|
||||
|
||||
const GeolocationControl = L.Control.extend({
|
||||
_containerEl: null as HTMLButtonElement | null,
|
||||
_currentLocationMarker: null as L.Marker | null,
|
||||
setCurrentLocationMarker: function (marker?: L.Marker) {
|
||||
if (marker) this._currentLocationMarker = marker;
|
||||
},
|
||||
onAdd: function (map: L.Map) {
|
||||
const locationButton = document.createElement("button");
|
||||
|
||||
this._containerEl = locationButton;
|
||||
|
||||
locationButton.id = "ask-permission-control-button";
|
||||
|
||||
locationButton.textContent = "Konumuma Git";
|
||||
|
||||
locationButton.classList.add("custom-map-control-button");
|
||||
|
||||
locationButton.type = "button";
|
||||
|
||||
L.DomEvent.on(locationButton, "click", () => {
|
||||
console.log(this._currentLocationMarker);
|
||||
if (this._currentLocationMarker) {
|
||||
map.setView(this._currentLocationMarker.getLatLng(), 12);
|
||||
}
|
||||
});
|
||||
|
||||
return locationButton;
|
||||
},
|
||||
onRemove: function () {
|
||||
L.DomEvent.off(this._containerEl!);
|
||||
},
|
||||
});
|
||||
|
||||
export { GoToTargetControl, GeolocationControl };
|
|
@ -1,63 +0,0 @@
|
|||
import Toastify from "toastify-js";
|
||||
import L from "leaflet";
|
||||
import { currentLocationIcon } from "./icons";
|
||||
|
||||
function onLocationError(err: L.ErrorEvent) {
|
||||
let errorMessage;
|
||||
switch (err.code) {
|
||||
case 1:
|
||||
errorMessage =
|
||||
"Konum izni alınamadı, lütfen tarayıcınızın ve cihazınızın gizlilik ayarlarını kontrol edin.";
|
||||
break;
|
||||
case 2:
|
||||
errorMessage =
|
||||
"Konumunuz tespit edilemedi, lütfen biraz sonra tekrar deneyiniz.";
|
||||
break;
|
||||
case 3:
|
||||
errorMessage =
|
||||
"Konum isteği zaman aşımına uğradı, lütfen sayfayı yenileyip tekrar deneyiniz.";
|
||||
break;
|
||||
default:
|
||||
errorMessage =
|
||||
"Konum izni alınamadı, lütfen tarayıcınızın ve cihazınızın gizlilik ayarlarını kontrol edin.";
|
||||
break;
|
||||
}
|
||||
|
||||
Toastify({
|
||||
text: errorMessage,
|
||||
duration: 3000,
|
||||
gravity: "top", // `top` or `bottom`
|
||||
position: "center", // `left`, `center` or `right`
|
||||
stopOnFocus: true, // Prevents dismissing of toast on hover
|
||||
style: {
|
||||
background: "black",
|
||||
borderRadius: "6px",
|
||||
margin: "16px",
|
||||
},
|
||||
onClick: function () {}, // Callback after click
|
||||
}).showToast();
|
||||
}
|
||||
|
||||
function onLocationSuccess(
|
||||
locationEvent: L.LocationEvent,
|
||||
map: L.Map,
|
||||
currentLocationMarker: L.Marker | undefined
|
||||
) {
|
||||
const position = locationEvent.latlng;
|
||||
|
||||
const currentPos = {
|
||||
lat: position.lat,
|
||||
lng: position.lng,
|
||||
};
|
||||
|
||||
if (currentLocationMarker) {
|
||||
currentLocationMarker.setLatLng(currentPos);
|
||||
} else {
|
||||
currentLocationMarker = L.marker(currentPos, {
|
||||
icon: currentLocationIcon,
|
||||
});
|
||||
currentLocationMarker.addTo(map);
|
||||
}
|
||||
}
|
||||
|
||||
export { onLocationError, onLocationSuccess };
|
|
@ -1,13 +0,0 @@
|
|||
import L from "leaflet";
|
||||
|
||||
var targetLocationIcon = L.icon({
|
||||
iconUrl: "goal.svg",
|
||||
iconSize: [32, 32],
|
||||
});
|
||||
|
||||
var currentLocationIcon = L.icon({
|
||||
iconUrl: "blue-dot.png",
|
||||
iconSize: [32, 32],
|
||||
});
|
||||
|
||||
export { targetLocationIcon, currentLocationIcon };
|
|
@ -1,31 +0,0 @@
|
|||
import { html } from "lit";
|
||||
|
||||
// Locked lock icon
|
||||
const lockSVG = html`<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="#ffffff"
|
||||
viewBox="0 0 256 256"
|
||||
>
|
||||
<path
|
||||
d="M208,80H176V56a48,48,0,0,0-96,0V80H48A16,16,0,0,0,32,96V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V96A16,16,0,0,0,208,80ZM96,56a32,32,0,0,1,64,0V80H96ZM208,208H48V96H208V208Zm-68-56a12,12,0,1,1-12-12A12,12,0,0,1,140,152Z"
|
||||
></path>
|
||||
</svg>`;
|
||||
|
||||
// Unlocked lock icon
|
||||
const unlockSVG = html`
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="#ffffff"
|
||||
viewBox="0 0 256 256"
|
||||
>
|
||||
<path
|
||||
d="M208,80H96V56a32,32,0,0,1,32-32c15.37,0,29.2,11,32.16,25.59a8,8,0,0,0,15.68-3.18C171.32,24.15,151.2,8,128,8A48.05,48.05,0,0,0,80,56V80H48A16,16,0,0,0,32,96V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V96A16,16,0,0,0,208,80Zm0,128H48V96H208V208Zm-68-56a12,12,0,1,1-12-12A12,12,0,0,1,140,152Z"
|
||||
></path>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
export { lockSVG, unlockSVG };
|
|
@ -1,95 +0,0 @@
|
|||
import { html } from "lit";
|
||||
import { lockSVG, unlockSVG } from "./icons";
|
||||
|
||||
// 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`
|
||||
<div class="flex flex-col justify-center gap-4 overlay">
|
||||
<button
|
||||
id="unlock-content-button"
|
||||
class="inline-flex items-center justify-center whitespace-nowrap font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-11 rounded-md text-lg p-6 text-md">
|
||||
İçerik Kilitli
|
||||
</button>
|
||||
<div class="rounded-lg border bg-card text-card-foreground shadow-sm p-2">
|
||||
<div class="pb-0 text-center flex flex-col gap-4">
|
||||
<p id="locked-content-description">
|
||||
Ne kadar yaklaştığını görmek için aşağıdaki butona bas.
|
||||
</p>
|
||||
<button
|
||||
@click="${onClickHandler}"
|
||||
class="inline-flex items-center justify-center whitespace-nowrap font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 text-primary-foreground h-9 rounded-md px-3 bg-green-700 hover:bg-green-600 text-md">
|
||||
Konum İzni Ver
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// This template is shown when user has not given permission
|
||||
function permissionDeniedButtonTemplate() {
|
||||
return html`<div class="flex flex-col justify-center gap-4 overlay">
|
||||
<button
|
||||
id="unlock-content-button"
|
||||
class="inline-flex gap-2 items-center justify-center whitespace-nowrap font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-11 rounded-md text-lg p-6 text-md">
|
||||
${lockSVG}
|
||||
<p>İçerik Kilitli</p>
|
||||
</button>
|
||||
<div class="rounded-lg border bg-card text-card-foreground shadow-sm p-2">
|
||||
<div class="pb-0 px-4 text-center">
|
||||
<p id="locked-content-description">
|
||||
Konumuna erişim izni vermediğin için hedefe ne kadar <br />
|
||||
yakın olduğun tespit edilemiyor.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// This template is shown when user has given permission but has not arrived yet
|
||||
function lockedButtonTemplate(proximityText: string | undefined) {
|
||||
return html`<div class="flex flex-col justify-center gap-4 overlay">
|
||||
<button
|
||||
id="unlock-content-button"
|
||||
class="inline-flex gap-2 items-center justify-center whitespace-nowrap font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-11 rounded-md text-lg p-6 text-md">
|
||||
${lockSVG}
|
||||
<p>İçerik Kilitli</p>
|
||||
</button>
|
||||
<div class="rounded-lg border bg-card text-card-foreground shadow-sm p-2">
|
||||
<div class="pb-0 px-4 text-center">
|
||||
<p id="locked-content-description">
|
||||
İçeriği görmek için konuma gitmelisin! Kalan mesafe: ${proximityText}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// 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(onClickHandler: () => void) {
|
||||
return html` <div class="flex flex-col justify-center gap-4 overlay">
|
||||
<button
|
||||
@click="${onClickHandler}"
|
||||
id="unlock-content-button"
|
||||
class="inline-flex gap-2 items-center justify-center whitespace-nowrap font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 text-primary-foreground h-11 rounded-md text-lg p-6 animate-pulse bg-indigo-600 hover:bg-indigo-700 hover:animate-none border-2 border-indigo-800 transition-1000">
|
||||
${unlockSVG}
|
||||
<p>İçeriğin Kilidi Açıldı</p>
|
||||
</button>
|
||||
|
||||
<div class="rounded-lg border bg-card text-card-foreground shadow-sm p-2">
|
||||
<div class="pb-0 px-4 text-center">
|
||||
<p id="locked-content-description">İçeriği görmek için butona bas!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
export {
|
||||
lockedButtonTemplate,
|
||||
unlockedButtonTemplate,
|
||||
permissionButtonTemplate,
|
||||
permissionDeniedButtonTemplate,
|
||||
};
|
|
@ -1,100 +0,0 @@
|
|||
// Lit
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
unsafeCSS,
|
||||
type CSSResultGroup,
|
||||
type PropertyValues,
|
||||
} from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators.js";
|
||||
|
||||
// Leaflet
|
||||
import L, { Map } from "leaflet";
|
||||
import type { LatLngTuple } from "leaflet";
|
||||
import { currentLocationIcon, targetLocationIcon } from "./LeafletMap/icons";
|
||||
import { GeolocationControl, GoToTargetControl } from "./LeafletMap/controls";
|
||||
|
||||
// Styles
|
||||
import leafletStyles from "leaflet/dist/leaflet.css?inline";
|
||||
import globalStyles from "@/styles/globals.css?inline";
|
||||
import mapStyles from "@/styles/locked-page.css?inline";
|
||||
|
||||
@customElement("leaflet-map")
|
||||
export class LeafletMap extends LitElement {
|
||||
// Styles
|
||||
static styles?: CSSResultGroup | undefined = [
|
||||
unsafeCSS(leafletStyles),
|
||||
unsafeCSS(globalStyles),
|
||||
unsafeCSS(mapStyles),
|
||||
];
|
||||
|
||||
// Div element to initialize Leaflet in
|
||||
@query("#mapid")
|
||||
_mapElement!: HTMLDivElement;
|
||||
|
||||
// Properties and states
|
||||
@property({ type: Object, noAccessor: true }) targetPosition?: LatLngTuple;
|
||||
@property({ type: Object })
|
||||
currentPosition?: LatLngTuple;
|
||||
|
||||
protected _map?: L.Map;
|
||||
protected _currentLocationMarker?: L.Marker;
|
||||
|
||||
firstUpdated(): void {
|
||||
if (!this._mapElement || !this.targetPosition) return;
|
||||
this._map = new Map(this._mapElement).setView(this.targetPosition, 13);
|
||||
|
||||
L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||
maxZoom: 19,
|
||||
attribution:
|
||||
'© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
||||
}).addTo(this._map);
|
||||
|
||||
// Add target location icon marker
|
||||
L.marker(this.targetPosition, { icon: targetLocationIcon }).addTo(
|
||||
this._map
|
||||
);
|
||||
|
||||
L.circle(this.targetPosition, {
|
||||
color: "blue",
|
||||
fillColor: "#30f",
|
||||
fillOpacity: 0.2,
|
||||
radius: 50,
|
||||
}).addTo(this._map);
|
||||
|
||||
// Add target location control
|
||||
const targetLocationControl = new GoToTargetControl({
|
||||
position: "bottomleft",
|
||||
});
|
||||
|
||||
targetLocationControl.setTargetLocation(this.targetPosition);
|
||||
targetLocationControl.addTo(this._map);
|
||||
}
|
||||
|
||||
protected update(changedProperties: PropertyValues): void {
|
||||
super.update(changedProperties);
|
||||
if (changedProperties.get("currentPosition")) {
|
||||
if (!this._currentLocationMarker && this._map) {
|
||||
this._currentLocationMarker = L.marker(this.currentPosition!, {
|
||||
icon: currentLocationIcon,
|
||||
});
|
||||
this._currentLocationMarker.addTo(this._map);
|
||||
|
||||
const geolocationControl = new GeolocationControl({
|
||||
position: "bottomleft",
|
||||
});
|
||||
|
||||
geolocationControl.setCurrentLocationMarker(
|
||||
this._currentLocationMarker
|
||||
);
|
||||
geolocationControl.addTo(this._map);
|
||||
} else if (this._currentLocationMarker) {
|
||||
this._currentLocationMarker.setLatLng(this.currentPosition!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<div id="mapid" class="w-full h-[450px] rounded"></div>`;
|
||||
}
|
||||
}
|
|
@ -1,151 +0,0 @@
|
|||
// Lit imports
|
||||
import { LitElement, html, nothing, unsafeCSS, type CSSResultGroup } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
|
||||
// Leaflet
|
||||
import { type LatLngTuple } from "leaflet";
|
||||
|
||||
// Styles
|
||||
import globalStyles from "@/styles/globals.css?inline";
|
||||
import lockedContentStyles from "../styles/locked-content.css?inline";
|
||||
|
||||
// Templates
|
||||
import {
|
||||
lockedButtonTemplate,
|
||||
permissionButtonTemplate,
|
||||
permissionDeniedButtonTemplate,
|
||||
unlockedButtonTemplate,
|
||||
} from "./LockedContent/templates";
|
||||
|
||||
// Geolocation utils
|
||||
import { calculateDistance } from "./LockedContent/geolocation";
|
||||
import { incrementUnlockCounter } from "./LockedContent/serverUtils";
|
||||
|
||||
// LockedContent is a custom element watching user location and blurring
|
||||
// given image until user has arrived a certain position
|
||||
@customElement("locked-content")
|
||||
export class LockedContent extends LitElement {
|
||||
// Constants
|
||||
geolocationOptions = {
|
||||
enableHighAccuracy: true,
|
||||
timeout: 15000,
|
||||
maximumAge: 0,
|
||||
};
|
||||
|
||||
// Tailwind and custom styles
|
||||
static styles: CSSResultGroup | undefined = [
|
||||
unsafeCSS(globalStyles),
|
||||
unsafeCSS(lockedContentStyles),
|
||||
];
|
||||
|
||||
// Components properties/attributes, no accessor attribute disables detecting
|
||||
// changes as these are readonly attributes 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;
|
||||
@property({ type: Object })
|
||||
currentPosition?: LatLngTuple;
|
||||
|
||||
// Reactive states, template is rendered according to this states
|
||||
@state()
|
||||
protected _unlocked = false;
|
||||
@state()
|
||||
protected _arrived = false;
|
||||
@state()
|
||||
protected _distanceText?: string;
|
||||
|
||||
private _updateDistanceText(distance: number) {
|
||||
// Update the proximity text according to the distance remaining
|
||||
if (distance > 1000) {
|
||||
this._distanceText = `${(distance / 1000).toFixed()} KM`;
|
||||
} else if (distance > 100) {
|
||||
this._distanceText = `${distance.toFixed(0)} M`;
|
||||
}
|
||||
}
|
||||
|
||||
private _checkArrived(distance: number) {
|
||||
// If target is close less then 100 meters user has arrived to target location
|
||||
if (distance < 100) {
|
||||
// Update state to reveal the image
|
||||
this._arrived = true;
|
||||
}
|
||||
}
|
||||
|
||||
private _dispatchAskPermissionEvent() {
|
||||
const event = new Event("askpermission", { bubbles: true, composed: true });
|
||||
this.dispatchEvent(event);
|
||||
}
|
||||
|
||||
// This template is shown when user hasn't give geolocation permission yet
|
||||
// When user click the button user is asked for geolocation permission
|
||||
private _permissionButtonTemplate = () =>
|
||||
permissionButtonTemplate(this._dispatchAskPermissionEvent);
|
||||
|
||||
// This template is shown when user has given permission but has not arrived yet
|
||||
private _lockedButtonTemplate = () => {
|
||||
// Target position must be set
|
||||
if (!this.targetPosition || !this.currentPosition) return;
|
||||
|
||||
// Calculate the distance between target and current position in meters
|
||||
const distance = calculateDistance(
|
||||
this.currentPosition,
|
||||
this.targetPosition
|
||||
);
|
||||
|
||||
// Update the text based on the distance
|
||||
this._updateDistanceText(distance);
|
||||
|
||||
this._checkArrived(distance);
|
||||
|
||||
return lockedButtonTemplate(this._distanceText);
|
||||
};
|
||||
|
||||
// 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
|
||||
private _unlockedButtonTemplate = () =>
|
||||
unlockedButtonTemplate(() => {
|
||||
incrementUnlockCounter(this.imageId);
|
||||
this._unlocked = true;
|
||||
});
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
}
|
||||
|
||||
render() {
|
||||
let buttonTemplate;
|
||||
|
||||
// Determine which template to show, there are 3 states:
|
||||
// 1 - No current location provided
|
||||
// 2 - Current location 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;
|
||||
} else if (this.currentPosition) {
|
||||
buttonTemplate = this._lockedButtonTemplate;
|
||||
} else if (!this.currentPosition) {
|
||||
buttonTemplate = this._permissionButtonTemplate;
|
||||
} else {
|
||||
buttonTemplate = permissionDeniedButtonTemplate;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="w-full h-[475px] overflow-hidden border border-zinc-200 shadow-sm p-4 rounded"
|
||||
>
|
||||
<div class="flex flex-col justify-center items-center image-wrapper">
|
||||
<img
|
||||
id="content"
|
||||
src="${this.imageURL}"
|
||||
class="h-[450px] ${this._unlocked ? "" : "blur-2xl"}"
|
||||
/>
|
||||
|
||||
${this._unlocked ? nothing : buttonTemplate()}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
|
@ -1,10 +1,6 @@
|
|||
import { type ClassValue, clsx } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
import { type ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
||||
export function remoteLog(data: any) {
|
||||
fetch("/api/debug", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) })
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user