From 73b458e998090e177bba42445f800df7de982b72 Mon Sep 17 00:00:00 2001
From: Alex Lion
Date: Mon, 3 Aug 2026 09:48:38 +0700
Subject: [PATCH] Use native datetime-local instead of AirDatepicker
---
assets/css/app.css | 15 ---
assets/js/app.js | 97 ++++++++-----------
assets/package-lock.json | 11 ---
assets/package.json | 1 -
.../event_live/event_form_component.html.heex | 4 +-
.../views/components/input_component.ex | 12 ++-
test/claper_web/live/event_live_test.exs | 7 ++
7 files changed, 58 insertions(+), 89 deletions(-)
diff --git a/assets/css/app.css b/assets/css/app.css
index 8f28801..8d40ea6 100644
--- a/assets/css/app.css
+++ b/assets/css/app.css
@@ -1,4 +1,3 @@
-@import 'air-datepicker/air-datepicker.css';
@import 'animate.css/animate.min.css';
@import 'tailwindcss';
@@ -545,20 +544,6 @@
transform:rotate(-20deg);
}
-/* Air datepicker */
-.air-datepicker-body--day-name {
- color: var(--color-primary-600);
-}
-
-.air-datepicker-cell.-selected-, .air-datepicker-cell.-selected-.-current- {
- background-color: var(--color-primary-500);
- color: var(--color-white);
-}
-
-.air-datepicker-cell.-current- {
- color: var(--color-secondary-500);
-}
-
.animate__slow_slow {
--animate-duration: 5s;
}
diff --git a/assets/js/app.js b/assets/js/app.js
index c6dcad4..779eee8 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -6,14 +6,6 @@ import { LiveSocket } from "phoenix_live_view";
import topbar from "../vendor/topbar";
import Alpine from "alpinejs";
import moment from "moment-timezone";
-import AirDatepicker from "air-datepicker";
-import airdatepickerLocaleEn from "air-datepicker/locale/en";
-import airdatepickerLocaleFr from "air-datepicker/locale/fr";
-import airdatepickerLocaleDe from "air-datepicker/locale/de";
-import airdatepickerLocaleEs from "air-datepicker/locale/es";
-import airdatepickerLocaleNl from "air-datepicker/locale/nl";
-import airdatepickerLocaleIt from "air-datepicker/locale/it";
-import airdatepickerLocaleHu from "air-datepicker/locale/hu";
import "moment/locale/de";
import "moment/locale/fr";
import "moment/locale/es";
@@ -42,43 +34,19 @@ const supportedLocales = window.claperConfig?.supportedLocales || [
"lv",
];
-const airdatePickrSupportedLocales = window.claperConfig?.supportedLocales || [
- "en",
- "fr",
- "de",
- "es",
- "nl",
- "it",
- "hu",
-];
-
var locale =
document.querySelector("html").getAttribute("lang") ||
navigator.language.split("-")[0];
-var airdatepickrLocale = locale;
-
if (!supportedLocales.includes(locale)) {
locale = "en";
}
-if (!airdatePickrSupportedLocales.includes(airdatepickrLocale)) {
- airdatepickrLocale = "en";
-}
window.moment.locale("en");
window.moment.locale(locale);
window.Alpine = Alpine;
Alpine.start();
-let airdatePickrLocales = {
- en: airdatepickerLocaleEn,
- fr: airdatepickerLocaleFr,
- de: airdatepickerLocaleDe,
- es: airdatepickerLocaleEs,
- nl: airdatepickerLocaleNl,
- it: airdatepickerLocaleIt,
- hu: airdatepickerLocaleHu,
-};
let csrfToken = document
.querySelector("meta[name='csrf-token']")
.getAttribute("content");
@@ -450,31 +418,52 @@ Hooks.CalendarLocalDate = {
this.el.innerHTML = moment.utc(this.el.dataset.date).local().calendar();
},
};
-Hooks.Pickr = {
+Hooks.DateTimeLocal = {
mounted() {
- const localTime = this.el.querySelector("input[type=text]");
- const utcTime = this.el.querySelector("input[type=hidden]");
- localTime.value = moment
- .utc(utcTime.value)
- .local()
- .format("DD-MM-YYYY HH:mm");
- this.pickr = new AirDatepicker(localTime, {
- dateFormat: "dd-MM-yyyy",
- timepicker: true,
- minutesStep: 5,
- minDate: moment(),
- timeFormat: "HH:mm",
- selectedDates: [moment(localTime.value, "DD-MM-YYYY HH:mm").toDate()],
- onSelect: ({ date }) => {
- const utc = moment(date).utc().format("YYYY-MM-DDTHH:mm:ss");
- utcTime.value = utc;
- },
- locale: airdatePickrLocales[airdatepickrLocale] || airdatePickrLocales["en"],
- });
+ this.localTime = this.el.querySelector("input[type=datetime-local]");
+ this.utcTime = this.el.querySelector("input[type=hidden]");
+ this.syncLocalTime();
+
+ this.handleInput = ({ target }) => {
+ if (target === this.localTime) this.syncUtcTime();
+ };
+ this.el.addEventListener("input", this.handleInput);
+ },
+ updated() {
+ this.localTime = this.el.querySelector("input[type=datetime-local]");
+ this.utcTime = this.el.querySelector("input[type=hidden]");
+ this.syncLocalTime();
+ },
+ syncLocalTime() {
+ if (!this.utcTime.value) {
+ this.localTime.value = "";
+ return;
+ }
+
+ const value = this.utcTime.value.replace(" ", "T").replace(/Z$/, "");
+ const date = new Date(`${value}Z`);
+
+ if (!Number.isNaN(date.getTime())) {
+ const pad = (part) => String(part).padStart(2, "0");
+ this.localTime.value =
+ `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}` +
+ `T${pad(date.getHours())}:${pad(date.getMinutes())}`;
+ }
+ },
+ syncUtcTime() {
+ if (!this.localTime.value) {
+ this.utcTime.value = "";
+ return;
+ }
+
+ const date = new Date(this.localTime.value);
+
+ if (!Number.isNaN(date.getTime())) {
+ this.utcTime.value = date.toISOString().slice(0, 19);
+ }
},
- updated() {},
destroyed() {
- if (this.pickr) this.pickr.destroy();
+ this.el.removeEventListener("input", this.handleInput);
},
};
Hooks.UpdateAttendees = {
diff --git a/assets/package-lock.json b/assets/package-lock.json
index fd979bd..4f9aa13 100644
--- a/assets/package-lock.json
+++ b/assets/package-lock.json
@@ -5,7 +5,6 @@
"packages": {
"": {
"dependencies": {
- "air-datepicker": "^3.5.0",
"animate.css": "^4.1.1",
"chart.js": "^4.4.0",
"chartjs-adapter-moment": "^1.0.1",
@@ -896,11 +895,6 @@
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==",
"dev": true
},
- "node_modules/air-datepicker": {
- "version": "3.5.0",
- "resolved": "https://registry.npmjs.org/air-datepicker/-/air-datepicker-3.5.0.tgz",
- "integrity": "sha512-WOpn1MaSl5drcXSwkXg5Gh/jXX/VFfamNnIb8E43AY4UKuW/bNEW06e3GGsiWLDBQLabD22L6b6cP7KHnAy54w=="
- },
"node_modules/alpinejs": {
"version": "3.13.8",
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.8.tgz",
@@ -1930,11 +1924,6 @@
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==",
"dev": true
},
- "air-datepicker": {
- "version": "3.5.0",
- "resolved": "https://registry.npmjs.org/air-datepicker/-/air-datepicker-3.5.0.tgz",
- "integrity": "sha512-WOpn1MaSl5drcXSwkXg5Gh/jXX/VFfamNnIb8E43AY4UKuW/bNEW06e3GGsiWLDBQLabD22L6b6cP7KHnAy54w=="
- },
"alpinejs": {
"version": "3.13.8",
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.8.tgz",
diff --git a/assets/package.json b/assets/package.json
index 756ad6f..6e6976a 100644
--- a/assets/package.json
+++ b/assets/package.json
@@ -11,7 +11,6 @@
"tailwindcss": "^4.1.11"
},
"dependencies": {
- "air-datepicker": "^3.5.0",
"animate.css": "^4.1.1",
"chart.js": "^4.4.0",
"chartjs-adapter-moment": "^1.0.1",
diff --git a/lib/claper_web/live/event_live/event_form_component.html.heex b/lib/claper_web/live/event_live/event_form_component.html.heex
index 1b61fdd..b6bc61a 100644
--- a/lib/claper_web/live/event_live/event_form_component.html.heex
+++ b/lib/claper_web/live/event_live/event_form_component.html.heex
@@ -259,14 +259,12 @@
-
+
{gettext("Shown in your local time.")}
diff --git a/lib/claper_web/views/components/input_component.ex b/lib/claper_web/views/components/input_component.ex
index b27b809..96d296e 100644
--- a/lib/claper_web/views/components/input_component.ex
+++ b/lib/claper_web/views/components/input_component.ex
@@ -277,14 +277,16 @@ defmodule ClaperWeb.Component.Input do
~H"""
-
- {label(@form, @key, @name, class: "block text-sm font-bold text-base-content")}
+