nodebb-plugin-calendar-onekite 2.1.2 → 10.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/language/en-GB/calendar-onekite.json +4 -0
- package/library.js +479 -741
- package/package.json +4 -17
- package/plugin.json +22 -18
- package/public/css/calendar-onekite.css +14 -0
- package/public/js/admin/calendar-onekite-admin.js +81 -0
- package/public/js/calendar-onekite.js +186 -0
- package/templates/admin/plugins/calendar-onekite.tpl +104 -65
- package/templates/calendar-onekite/calendar.tpl +18 -0
- package/.drone.yml +0 -62
- package/README.md +0 -28
- package/helloasso.js +0 -129
- package/static/css/calendar-onekite.css +0 -4
- package/static/js/admin-planning.bundle.js +0 -26
- package/static/js/admin-planning.js +0 -133
- package/static/js/admin.bundle.js +0 -26
- package/static/js/admin.js +0 -193
- package/static/js/calendar-my-reservations.js +0 -55
- package/static/js/calendar.bundle.js +0 -63
- package/static/js/calendar.js +0 -454
- package/static/js/my-reservations.bundle.js +0 -42
- package/templates/admin/calendar-planning.tpl +0 -33
- package/templates/calendar-my-reservations.tpl +0 -31
- package/templates/calendar.tpl +0 -104
- package/templates/emails/calendar-payment-confirmed.tpl +0 -1
- package/templates/emails/calendar-reservation-approved.tpl +0 -1
- package/templates/emails/calendar-reservation-created.tpl +0 -1
- package/templates/widgets/calendar-upcoming.tpl +0 -14
package/package.json
CHANGED
|
@@ -1,23 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodebb-plugin-calendar-onekite",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "10.0.11",
|
|
4
|
+
"description": "NodeBB calendar booking plugin using FullCalendar and HelloAsso checkout intents",
|
|
5
5
|
"main": "library.js",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"plugin",
|
|
10
|
-
"calendar",
|
|
11
|
-
"booking",
|
|
12
|
-
"helloasso",
|
|
13
|
-
"fullcalendar"
|
|
14
|
-
],
|
|
15
|
-
"engines": {
|
|
16
|
-
"node": ">=18"
|
|
17
|
-
},
|
|
18
|
-
"dependencies": {},
|
|
19
|
-
"scripts": {
|
|
20
|
-
"lint": "echo \"(optional) add eslint if you want\"",
|
|
21
|
-
"test": "echo \"no tests\""
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"axios": "^1.7.7"
|
|
22
9
|
}
|
|
23
10
|
}
|
package/plugin.json
CHANGED
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "nodebb-plugin-calendar-onekite",
|
|
3
|
-
"name": "Calendar
|
|
4
|
-
"description": "
|
|
5
|
-
"url": "",
|
|
6
|
-
"version": "2.1.2",
|
|
3
|
+
"name": "Calendar OneKite",
|
|
4
|
+
"description": "FullCalendar-based equipment booking workflow with HelloAsso checkout + admin approval.",
|
|
5
|
+
"url": "https://www.onekite.com/calendar",
|
|
7
6
|
"library": "./library.js",
|
|
8
|
-
"staticDirs": {
|
|
9
|
-
"static": "static"
|
|
10
|
-
},
|
|
11
|
-
"acpScripts": [
|
|
12
|
-
"static/js/admin.bundle.js",
|
|
13
|
-
"static/js/admin-planning.bundle.js"
|
|
14
|
-
],
|
|
15
7
|
"hooks": [
|
|
16
8
|
{
|
|
17
9
|
"hook": "static:app.load",
|
|
@@ -22,13 +14,25 @@
|
|
|
22
14
|
"method": "addAdminNavigation"
|
|
23
15
|
},
|
|
24
16
|
{
|
|
25
|
-
"hook": "filter:
|
|
26
|
-
"method": "
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
"hook": "filter:widget.render:calendarUpcoming",
|
|
30
|
-
"method": "renderUpcomingWidget"
|
|
17
|
+
"hook": "filter:router.page",
|
|
18
|
+
"method": "addPageRoute"
|
|
31
19
|
}
|
|
32
20
|
],
|
|
33
|
-
"templates": "templates"
|
|
21
|
+
"templates": "templates",
|
|
22
|
+
"staticDirs": {
|
|
23
|
+
"public": "./public"
|
|
24
|
+
},
|
|
25
|
+
"acpScripts": [
|
|
26
|
+
"public/js/admin/calendar-onekite-admin.js"
|
|
27
|
+
],
|
|
28
|
+
"scripts": [
|
|
29
|
+
"public/js/calendar-onekite.js"
|
|
30
|
+
],
|
|
31
|
+
"styles": [
|
|
32
|
+
"public/css/calendar-onekite.css"
|
|
33
|
+
],
|
|
34
|
+
"languages": [
|
|
35
|
+
"language"
|
|
36
|
+
],
|
|
37
|
+
"defaultLang": "en-GB"
|
|
34
38
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
.calendar-onekite-page {
|
|
2
|
+
padding: 0.5rem 0;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.calendar-onekite-header {
|
|
6
|
+
margin-top: 0.25rem;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
#onekite-calendar {
|
|
10
|
+
background: var(--bs-body-bg);
|
|
11
|
+
border: 1px solid var(--bs-border-color);
|
|
12
|
+
border-radius: 0.75rem;
|
|
13
|
+
padding: 0.5rem;
|
|
14
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/* global $, app, socket */
|
|
2
|
+
|
|
3
|
+
(function () {
|
|
4
|
+
function setStatus(html, type) {
|
|
5
|
+
const el = $('#calendar-onekite-admin-status');
|
|
6
|
+
el.html(`<div class="alert alert-${type || 'info'}">${html}</div>`);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async function loadSettings() {
|
|
10
|
+
const settings = await $.getJSON('/api/admin/plugins/calendar-onekite');
|
|
11
|
+
// Populate
|
|
12
|
+
$('#calendar-onekite-settings [name="enabled"]').prop('checked', settings.enabled === 'on' || settings.enabled === true);
|
|
13
|
+
$('#calendar-onekite-settings [name="requesterGroups"]').val(settings.requesterGroups || '');
|
|
14
|
+
$('#calendar-onekite-settings [name="approverGroups"]').val(settings.approverGroups || '');
|
|
15
|
+
$('#calendar-onekite-settings [name="holdMinutes"]').val(settings.holdMinutes || 60);
|
|
16
|
+
$('#calendar-onekite-settings [name="notifyEmails"]').val(settings.notifyEmails || '');
|
|
17
|
+
|
|
18
|
+
$('#calendar-onekite-settings [name="helloassoEnv"]').val(settings.helloassoEnv || 'sandbox');
|
|
19
|
+
$('#calendar-onekite-settings [name="helloassoClientId"]').val(settings.helloassoClientId || '');
|
|
20
|
+
$('#calendar-onekite-settings [name="helloassoClientSecret"]').val(settings.helloassoClientSecret || '');
|
|
21
|
+
$('#calendar-onekite-settings [name="helloassoOrganizationSlug"]').val(settings.helloassoOrganizationSlug || '');
|
|
22
|
+
$('#calendar-onekite-settings [name="helloassoFormType"]').val(settings.helloassoFormType || 'event');
|
|
23
|
+
$('#calendar-onekite-settings [name="helloassoFormSlug"]').val(settings.helloassoFormSlug || '');
|
|
24
|
+
$('#calendar-onekite-settings [name="helloassoBackUrl"]').val(settings.helloassoBackUrl || '');
|
|
25
|
+
$('#calendar-onekite-settings [name="helloassoErrorUrl"]').val(settings.helloassoErrorUrl || '');
|
|
26
|
+
$('#calendar-onekite-settings [name="helloassoReturnUrl"]').val(settings.helloassoReturnUrl || '');
|
|
27
|
+
$('#calendar-onekite-settings [name="itemsCacheMinutes"]').val(settings.itemsCacheMinutes || 360);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function saveSettings(e) {
|
|
31
|
+
e.preventDefault();
|
|
32
|
+
const data = {};
|
|
33
|
+
$('#calendar-onekite-settings').serializeArray().forEach(({ name, value }) => {
|
|
34
|
+
data[name] = value;
|
|
35
|
+
});
|
|
36
|
+
data.enabled = $('#calendar-onekite-settings [name="enabled"]').prop('checked') ? 'on' : '';
|
|
37
|
+
await $.ajax({
|
|
38
|
+
url: '/api/admin/plugins/calendar-onekite',
|
|
39
|
+
method: 'POST',
|
|
40
|
+
data,
|
|
41
|
+
});
|
|
42
|
+
setStatus('Sauvegardé ✅', 'success');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function testItems() {
|
|
46
|
+
try {
|
|
47
|
+
const res = await $.getJSON('/api/calendar-onekite/items');
|
|
48
|
+
const items = res.items || [];
|
|
49
|
+
setStatus(`Items récupérés: <strong>${items.length}</strong>`, 'info');
|
|
50
|
+
} catch (e) {
|
|
51
|
+
setStatus(`Erreur items: ${e.responseJSON && e.responseJSON.error ? e.responseJSON.error : e.statusText}`, 'danger');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function purgeYear() {
|
|
56
|
+
const year = parseInt($('#calendar-onekite-purge-year').val(), 10);
|
|
57
|
+
if (!year) {
|
|
58
|
+
return setStatus('Année invalide', 'warning');
|
|
59
|
+
}
|
|
60
|
+
if (!confirm(`Purger toutes les réservations dont le début est en ${year} ?`)) return;
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const res = await $.ajax({
|
|
64
|
+
url: '/api/admin/plugins/calendar-onekite/purge',
|
|
65
|
+
method: 'POST',
|
|
66
|
+
data: { year },
|
|
67
|
+
});
|
|
68
|
+
setStatus(`Purge terminée. Supprimés: <strong>${res.deleted}</strong>`, 'success');
|
|
69
|
+
} catch (e) {
|
|
70
|
+
setStatus(`Erreur purge: ${e.responseJSON && e.responseJSON.error ? e.responseJSON.error : e.statusText}`, 'danger');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
$(window).on('action:admin.plugins.calendar-onekite', async function () {
|
|
75
|
+
await loadSettings();
|
|
76
|
+
|
|
77
|
+
$('#calendar-onekite-settings').off('submit').on('submit', saveSettings);
|
|
78
|
+
$('#calendar-onekite-test-items').off('click').on('click', testItems);
|
|
79
|
+
$('#calendar-onekite-purge').off('click').on('click', purgeYear);
|
|
80
|
+
});
|
|
81
|
+
})();
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/* global $, app */
|
|
2
|
+
(function () {
|
|
3
|
+
async function fetchItems() {
|
|
4
|
+
const res = await $.getJSON('/api/calendar-onekite/items');
|
|
5
|
+
return res.items || [];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async function createReservation(payload) {
|
|
9
|
+
const res = await $.ajax({
|
|
10
|
+
url: '/api/calendar-onekite/reservations',
|
|
11
|
+
method: 'POST',
|
|
12
|
+
data: payload,
|
|
13
|
+
});
|
|
14
|
+
return res.reservation;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function approveReservation(id) {
|
|
18
|
+
const res = await $.ajax({
|
|
19
|
+
url: `/api/calendar-onekite/reservations/${encodeURIComponent(id)}/approve`,
|
|
20
|
+
method: 'POST',
|
|
21
|
+
});
|
|
22
|
+
return res.reservation;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function rejectReservation(id) {
|
|
26
|
+
const res = await $.ajax({
|
|
27
|
+
url: `/api/calendar-onekite/reservations/${encodeURIComponent(id)}/reject`,
|
|
28
|
+
method: 'POST',
|
|
29
|
+
});
|
|
30
|
+
return res.reservation;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function loadFullCalendar() {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
if (window.FullCalendar) return resolve();
|
|
36
|
+
const css = document.createElement('link');
|
|
37
|
+
css.rel = 'stylesheet';
|
|
38
|
+
css.href = 'https://cdn.jsdelivr.net/npm/fullcalendar@6.1.15/index.global.min.css';
|
|
39
|
+
document.head.appendChild(css);
|
|
40
|
+
|
|
41
|
+
const script = document.createElement('script');
|
|
42
|
+
script.src = 'https://cdn.jsdelivr.net/npm/fullcalendar@6.1.15/index.global.min.js';
|
|
43
|
+
script.onload = resolve;
|
|
44
|
+
script.onerror = reject;
|
|
45
|
+
document.head.appendChild(script);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function initCalendar() {
|
|
50
|
+
await loadFullCalendar();
|
|
51
|
+
|
|
52
|
+
const uid = parseInt(app.user && app.user.uid, 10) || 0;
|
|
53
|
+
if (!uid) $('#onekite-login-hint').show();
|
|
54
|
+
|
|
55
|
+
let items = [];
|
|
56
|
+
try {
|
|
57
|
+
items = await fetchItems();
|
|
58
|
+
} catch (e) {
|
|
59
|
+
console.warn('Unable to fetch items', e);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function promptForItem() {
|
|
63
|
+
if (!items.length) {
|
|
64
|
+
const itemName = prompt('Nom du matériel ?');
|
|
65
|
+
if (!itemName) return null;
|
|
66
|
+
return { id: itemName, name: itemName, priceCents: 0 };
|
|
67
|
+
}
|
|
68
|
+
const choices = items
|
|
69
|
+
.map((it, idx) => {
|
|
70
|
+
const id = it.id ?? it.itemId ?? it.itemID ?? idx;
|
|
71
|
+
const name = it.name ?? it.label ?? it.itemName ?? `Item ${id}`;
|
|
72
|
+
const price = it.price ?? it.amount ?? it.unitPrice ?? it.totalAmount ?? it.publicAmount ?? 0;
|
|
73
|
+
return { id: String(id), name: String(name), priceCents: Number(price) || 0 };
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const menu = choices.map((c, i) => `${i + 1}) ${c.name}${c.priceCents ? ` — ${(c.priceCents / 100).toFixed(2)}€` : ''}`).join('\n');
|
|
77
|
+
const input = prompt(`Choisis un matériel:\n${menu}\n\nEntre le numéro:`);
|
|
78
|
+
const n = parseInt(input, 10);
|
|
79
|
+
if (!n || n < 1 || n > choices.length) return null;
|
|
80
|
+
return choices[n - 1];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const calendarEl = document.getElementById('onekite-calendar');
|
|
84
|
+
const calendar = new FullCalendar.Calendar(calendarEl, {
|
|
85
|
+
initialView: 'dayGridMonth',
|
|
86
|
+
selectable: true,
|
|
87
|
+
editable: false,
|
|
88
|
+
height: 'auto',
|
|
89
|
+
headerToolbar: {
|
|
90
|
+
left: 'prev,next today',
|
|
91
|
+
center: 'title',
|
|
92
|
+
right: 'dayGridMonth,timeGridWeek,timeGridDay',
|
|
93
|
+
},
|
|
94
|
+
events: async (info, success, failure) => {
|
|
95
|
+
try {
|
|
96
|
+
const res = await $.getJSON('/api/calendar-onekite/events', {
|
|
97
|
+
start: info.startStr,
|
|
98
|
+
end: info.endStr,
|
|
99
|
+
});
|
|
100
|
+
success(res.events || []);
|
|
101
|
+
} catch (e) {
|
|
102
|
+
failure(e);
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
select: async (selectionInfo) => {
|
|
106
|
+
try {
|
|
107
|
+
if (!uid) {
|
|
108
|
+
app.alertError('Connecte-toi pour réserver.');
|
|
109
|
+
calendar.unselect();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const item = promptForItem();
|
|
113
|
+
if (!item) {
|
|
114
|
+
calendar.unselect();
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const payload = {
|
|
118
|
+
itemId: item.id,
|
|
119
|
+
itemName: item.name,
|
|
120
|
+
start: selectionInfo.startStr,
|
|
121
|
+
end: selectionInfo.endStr,
|
|
122
|
+
priceCents: item.priceCents || 0,
|
|
123
|
+
};
|
|
124
|
+
await createReservation(payload);
|
|
125
|
+
app.alertSuccess('Demande envoyée (en attente de validation).');
|
|
126
|
+
calendar.refetchEvents();
|
|
127
|
+
} catch (e) {
|
|
128
|
+
const err = (e.responseJSON && e.responseJSON.error) ? e.responseJSON.error : e.statusText;
|
|
129
|
+
if (err === 'overlap') {
|
|
130
|
+
app.alertError('Ce matériel est déjà réservé sur cette période.');
|
|
131
|
+
} else if (err === 'not-allowed') {
|
|
132
|
+
app.alertError('Vous n’êtes pas autorisé à créer une demande.');
|
|
133
|
+
} else {
|
|
134
|
+
app.alertError(`Erreur: ${err}`);
|
|
135
|
+
}
|
|
136
|
+
} finally {
|
|
137
|
+
calendar.unselect();
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
eventClick: async (clickInfo) => {
|
|
141
|
+
const ev = clickInfo.event;
|
|
142
|
+
const p = ev.extendedProps || {};
|
|
143
|
+
// Approve/reject only if pending, and user has rights (server will enforce)
|
|
144
|
+
if (p.status !== 'pending') return;
|
|
145
|
+
|
|
146
|
+
if (!uid) return;
|
|
147
|
+
|
|
148
|
+
const action = prompt('Réservation en attente. Tape "a" pour valider, "r" pour refuser, autre pour annuler.');
|
|
149
|
+
if (action === 'a') {
|
|
150
|
+
try {
|
|
151
|
+
const updated = await approveReservation(ev.id);
|
|
152
|
+
app.alertSuccess('Validée ✅. Un email avec lien de paiement a été envoyé.');
|
|
153
|
+
calendar.refetchEvents();
|
|
154
|
+
if (updated && updated.paymentUrl) {
|
|
155
|
+
// optionally open link
|
|
156
|
+
const open = confirm('Ouvrir le lien de paiement ?');
|
|
157
|
+
if (open) window.open(updated.paymentUrl, '_blank');
|
|
158
|
+
}
|
|
159
|
+
} catch (e) {
|
|
160
|
+
const err = (e.responseJSON && e.responseJSON.error) ? e.responseJSON.error : e.statusText;
|
|
161
|
+
app.alertError(`Erreur validation: ${err}`);
|
|
162
|
+
}
|
|
163
|
+
} else if (action === 'r') {
|
|
164
|
+
try {
|
|
165
|
+
await rejectReservation(ev.id);
|
|
166
|
+
app.alertSuccess('Refusée ❌');
|
|
167
|
+
calendar.refetchEvents();
|
|
168
|
+
} catch (e) {
|
|
169
|
+
const err = (e.responseJSON && e.responseJSON.error) ? e.responseJSON.error : e.statusText;
|
|
170
|
+
app.alertError(`Erreur refus: ${err}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
calendar.render();
|
|
177
|
+
|
|
178
|
+
$('#onekite-refresh').on('click', () => calendar.refetchEvents());
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
$(window).on('action:ajaxify.end', function (ev, data) {
|
|
182
|
+
if (data && data.url && data.url.startsWith('calendar')) {
|
|
183
|
+
initCalendar();
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
})();
|
|
@@ -1,78 +1,117 @@
|
|
|
1
|
-
<div
|
|
2
|
-
<
|
|
1
|
+
<div class="acp-page-container">
|
|
2
|
+
<h1 class="mb-4">Calendar OneKite</h1>
|
|
3
3
|
|
|
4
|
-
<
|
|
5
|
-
|
|
4
|
+
<div class="alert alert-warning">
|
|
5
|
+
Les groupes sont à renseigner par <strong>noms de groupes</strong> séparés par des virgules (ex: <code>administrators, booking-approvers</code>).
|
|
6
|
+
</div>
|
|
6
7
|
|
|
7
|
-
<
|
|
8
|
+
<form id="calendar-onekite-settings">
|
|
9
|
+
<div class="mb-3">
|
|
10
|
+
<label class="form-label">Activer le plugin</label>
|
|
11
|
+
<div class="form-check">
|
|
12
|
+
<input class="form-check-input" type="checkbox" id="enabled" name="enabled">
|
|
13
|
+
<label class="form-check-label" for="enabled">Enabled</label>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
8
16
|
|
|
9
|
-
|
|
17
|
+
<div class="row g-3">
|
|
18
|
+
<div class="col-md-6">
|
|
19
|
+
<label class="form-label">Groupes autorisés à créer une demande (requesterGroups)</label>
|
|
20
|
+
<input class="form-control" type="text" name="requesterGroups" placeholder="members, ..." />
|
|
21
|
+
</div>
|
|
22
|
+
<div class="col-md-6">
|
|
23
|
+
<label class="form-label">Groupes qui valident/refusent (approverGroups)</label>
|
|
24
|
+
<input class="form-control" type="text" name="approverGroups" placeholder="administrators, ..." />
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
10
27
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
28
|
+
<div class="row g-3 mt-1">
|
|
29
|
+
<div class="col-md-6">
|
|
30
|
+
<label class="form-label">Durée de blocage d'une demande en attente (minutes)</label>
|
|
31
|
+
<input class="form-control" type="number" name="holdMinutes" min="1" value="60" />
|
|
32
|
+
</div>
|
|
33
|
+
<div class="col-md-6">
|
|
34
|
+
<label class="form-label">Emails supplémentaires à notifier (optionnel)</label>
|
|
35
|
+
<input class="form-control" type="text" name="notifyEmails" placeholder="[email protected], [email protected]" />
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
16
38
|
|
|
17
|
-
|
|
18
|
-
<label>Groupes autorisés à réserver</label>
|
|
19
|
-
<input id="calendar-onekite-book-groups" class="form-control" value="{settings.allowedBookingGroups}">
|
|
20
|
-
<small>Ex : registered-users, membres, vip</small>
|
|
21
|
-
</div>
|
|
39
|
+
<hr class="my-4"/>
|
|
22
40
|
|
|
23
|
-
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
41
|
+
<h3 class="h5">HelloAsso</h3>
|
|
42
|
+
<div class="row g-3">
|
|
43
|
+
<div class="col-md-4">
|
|
44
|
+
<label class="form-label">Environnement</label>
|
|
45
|
+
<select class="form-select" name="helloassoEnv">
|
|
46
|
+
<option value="sandbox">sandbox</option>
|
|
47
|
+
<option value="prod">prod</option>
|
|
48
|
+
</select>
|
|
49
|
+
</div>
|
|
50
|
+
<div class="col-md-4">
|
|
51
|
+
<label class="form-label">Client ID</label>
|
|
52
|
+
<input class="form-control" type="text" name="helloassoClientId" />
|
|
53
|
+
</div>
|
|
54
|
+
<div class="col-md-4">
|
|
55
|
+
<label class="form-label">Client Secret</label>
|
|
56
|
+
<input class="form-control" type="password" name="helloassoClientSecret" />
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
27
59
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
60
|
+
<div class="row g-3 mt-1">
|
|
61
|
+
<div class="col-md-4">
|
|
62
|
+
<label class="form-label">organizationSlug</label>
|
|
63
|
+
<input class="form-control" type="text" name="helloassoOrganizationSlug" />
|
|
64
|
+
</div>
|
|
65
|
+
<div class="col-md-4">
|
|
66
|
+
<label class="form-label">formType</label>
|
|
67
|
+
<input class="form-control" type="text" name="helloassoFormType" placeholder="event, membership, donation..." />
|
|
68
|
+
</div>
|
|
69
|
+
<div class="col-md-4">
|
|
70
|
+
<label class="form-label">formSlug</label>
|
|
71
|
+
<input class="form-control" type="text" name="helloassoFormSlug" />
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
33
74
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
75
|
+
<div class="row g-3 mt-1">
|
|
76
|
+
<div class="col-md-4">
|
|
77
|
+
<label class="form-label">backUrl (HTTPS)</label>
|
|
78
|
+
<input class="form-control" type="text" name="helloassoBackUrl" />
|
|
79
|
+
</div>
|
|
80
|
+
<div class="col-md-4">
|
|
81
|
+
<label class="form-label">errorUrl (HTTPS)</label>
|
|
82
|
+
<input class="form-control" type="text" name="helloassoErrorUrl" />
|
|
83
|
+
</div>
|
|
84
|
+
<div class="col-md-4">
|
|
85
|
+
<label class="form-label">returnUrl (HTTPS)</label>
|
|
86
|
+
<input class="form-control" type="text" name="helloassoReturnUrl" />
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
39
89
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
90
|
+
<div class="row g-3 mt-1">
|
|
91
|
+
<div class="col-md-4">
|
|
92
|
+
<label class="form-label">Cache items HelloAsso (minutes)</label>
|
|
93
|
+
<input class="form-control" type="number" name="itemsCacheMinutes" min="1" value="360" />
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
45
96
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
<small>Ex: https://api.helloasso.com</small>
|
|
52
|
-
</div>
|
|
53
|
-
<div class="form-group">
|
|
54
|
-
<label>Organization slug</label>
|
|
55
|
-
<input id="calendar-onekite-helloasso-org" class="form-control" value="{settings.helloassoOrganizationSlug}">
|
|
56
|
-
</div>
|
|
57
|
-
<div class="form-group">
|
|
58
|
-
<label>Form slug (Checkout)</label>
|
|
59
|
-
<input id="calendar-onekite-helloasso-form" class="form-control" value="{settings.helloassoFormSlug}">
|
|
60
|
-
</div>
|
|
61
|
-
<div class="form-group">
|
|
62
|
-
<label>Client ID</label>
|
|
63
|
-
<input id="calendar-onekite-helloasso-clientid" class="form-control" value="{settings.helloassoClientId}">
|
|
64
|
-
</div>
|
|
65
|
-
<div class="form-group">
|
|
66
|
-
<label>Client Secret</label>
|
|
67
|
-
<input id="calendar-onekite-helloasso-secret" class="form-control" value="{settings.helloassoClientSecret}">
|
|
68
|
-
</div>
|
|
69
|
-
<div class="form-group">
|
|
70
|
-
<label>Return URL</label>
|
|
71
|
-
<input id="calendar-onekite-helloasso-return" class="form-control" value="{settings.helloassoReturnUrl}">
|
|
72
|
-
</div>
|
|
97
|
+
<div class="mt-4 d-flex gap-2">
|
|
98
|
+
<button class="btn btn-primary" type="submit">Sauvegarder</button>
|
|
99
|
+
<button class="btn btn-outline-secondary" type="button" id="calendar-onekite-test-items">Tester la récupération des items</button>
|
|
100
|
+
</div>
|
|
101
|
+
</form>
|
|
73
102
|
|
|
74
|
-
<
|
|
75
|
-
</div>
|
|
103
|
+
<hr class="my-4"/>
|
|
76
104
|
|
|
105
|
+
<h3 class="h5">Purge calendrier</h3>
|
|
106
|
+
<div class="row g-2 align-items-end">
|
|
107
|
+
<div class="col-md-3">
|
|
108
|
+
<label class="form-label">Année</label>
|
|
109
|
+
<input class="form-control" type="number" id="calendar-onekite-purge-year" min="1970" max="3000" placeholder="2025" />
|
|
110
|
+
</div>
|
|
111
|
+
<div class="col-md-3">
|
|
112
|
+
<button class="btn btn-danger" type="button" id="calendar-onekite-purge">Purger</button>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
77
115
|
|
|
78
|
-
<
|
|
116
|
+
<div class="mt-3" id="calendar-onekite-admin-status"></div>
|
|
117
|
+
</div>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<div class="calendar-onekite-page">
|
|
2
|
+
<div class="calendar-onekite-header d-flex align-items-center justify-content-between flex-wrap gap-2">
|
|
3
|
+
<h1 class="h3 mb-0">Calendrier</h1>
|
|
4
|
+
<div class="calendar-onekite-actions d-flex gap-2 align-items-center">
|
|
5
|
+
<button class="btn btn-sm btn-outline-secondary" id="onekite-refresh">Rafraîchir</button>
|
|
6
|
+
</div>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<div class="alert alert-info mt-3" id="onekite-login-hint" style="display:none;">
|
|
10
|
+
Vous devez être connecté pour faire une demande de réservation.
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div class="mt-3" id="onekite-calendar"></div>
|
|
14
|
+
|
|
15
|
+
<div class="mt-3 small text-muted">
|
|
16
|
+
<span>⏳ = en attente de validation</span> · <span>✅ = validée</span> · <span>❌ = refusée</span>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
package/.drone.yml
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
kind: pipeline
|
|
2
|
-
type: docker
|
|
3
|
-
name: deploy-nodebb-plugin
|
|
4
|
-
|
|
5
|
-
trigger:
|
|
6
|
-
branch:
|
|
7
|
-
- master
|
|
8
|
-
event:
|
|
9
|
-
- push
|
|
10
|
-
|
|
11
|
-
steps:
|
|
12
|
-
- name: sanity-check
|
|
13
|
-
image: node:20-alpine
|
|
14
|
-
commands:
|
|
15
|
-
- node -v
|
|
16
|
-
- test -f plugin.json
|
|
17
|
-
- test -f library.js
|
|
18
|
-
- test -d static
|
|
19
|
-
- test -d templates
|
|
20
|
-
|
|
21
|
-
- name: deploy-files
|
|
22
|
-
image: appleboy/drone-scp
|
|
23
|
-
settings:
|
|
24
|
-
host:
|
|
25
|
-
from_secret: SSH_HOST
|
|
26
|
-
username:
|
|
27
|
-
from_secret: SSH_USER
|
|
28
|
-
port:
|
|
29
|
-
from_secret: SSH_PORT
|
|
30
|
-
key:
|
|
31
|
-
from_secret: SSH_KEY
|
|
32
|
-
source:
|
|
33
|
-
- ./
|
|
34
|
-
target:
|
|
35
|
-
from_secret: PLUGIN_TARGET_DIR
|
|
36
|
-
strip_components: 0
|
|
37
|
-
overwrite: true
|
|
38
|
-
|
|
39
|
-
- name: install-and-build
|
|
40
|
-
image: appleboy/drone-ssh
|
|
41
|
-
environment:
|
|
42
|
-
PLUGIN_TARGET_DIR:
|
|
43
|
-
from_secret: PLUGIN_TARGET_DIR
|
|
44
|
-
settings:
|
|
45
|
-
host:
|
|
46
|
-
from_secret: SSH_HOST
|
|
47
|
-
username:
|
|
48
|
-
from_secret: SSH_USER
|
|
49
|
-
port:
|
|
50
|
-
from_secret: SSH_PORT
|
|
51
|
-
key:
|
|
52
|
-
from_secret: SSH_KEY
|
|
53
|
-
script:
|
|
54
|
-
- 'echo "Plugin: $PLUGIN_TARGET_DIR"'
|
|
55
|
-
- 'test -d "$PLUGIN_TARGET_DIR"'
|
|
56
|
-
|
|
57
|
-
- 'cd "$PLUGIN_TARGET_DIR"'
|
|
58
|
-
- 'if [ -f package.json ]; then npm ci --omit=dev; else echo "No package.json, skipping npm ci"; fi'
|
|
59
|
-
|
|
60
|
-
#- 'cd "$NODEBB_PATH"'
|
|
61
|
-
#- './nodebb build'
|
|
62
|
-
#- './nodebb restart || true'
|
package/README.md
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# nodebb-plugin-calendar-onekite (no-jQuery)
|
|
2
|
-
|
|
3
|
-
## Features
|
|
4
|
-
- FullCalendar frontend (Month/Week/Day/List) with drag&drop + resize (editable for authorized groups)
|
|
5
|
-
- Event CRUD, booking (multi-day), admin validation workflow, HelloAsso payment intent + webhook
|
|
6
|
-
- Admin pages (ACP) without jQuery; robust under ajaxify via MutationObserver
|
|
7
|
-
- Widget: calendarUpcoming
|
|
8
|
-
|
|
9
|
-
## Important: FullCalendar assets
|
|
10
|
-
This package loads FullCalendar from jsDelivr CDN in templates/calendar.tpl.
|
|
11
|
-
If you need offline / no-CDN, tell me and I will vendor the files under static/vendor/fullcalendar.
|
|
12
|
-
|
|
13
|
-
## Install
|
|
14
|
-
- Copy into your NodeBB plugins folder
|
|
15
|
-
- Activate plugin in ACP
|
|
16
|
-
- Rebuild: ./nodebb build
|
|
17
|
-
|
|
18
|
-
## Routes
|
|
19
|
-
- Pages: /calendar, /calendar/my-reservations
|
|
20
|
-
- Admin: /admin/plugins/calendar-onekite, /admin/calendar/planning
|
|
21
|
-
- API: available under /api/... and /api/v3/... (NodeBB v4 client helper uses /api/v3)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
## v2.1 inventory model
|
|
25
|
-
- Global locations/inventory in ACP (locationsJson/inventoryJson)
|
|
26
|
-
- Events select allowed inventory item IDs (bookingItemIds)
|
|
27
|
-
- Availability is global per (itemId, locationId) across all events
|
|
28
|
-
- Admin planning is graphical (FullCalendar) with filters
|