nodebb-plugin-onekite-calendar 2.0.47 → 2.0.49

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/lib/api.js CHANGED
@@ -924,7 +924,9 @@ api.getCapabilities = async function (req, res) {
924
924
  canModerate: canMod,
925
925
  canCreateSpecial: uid ? await canCreateSpecial(uid, settings) : false,
926
926
  canDeleteSpecial: uid ? await canDeleteSpecial(uid, settings) : false,
927
+ // Outings share the same rights as reservations/locations.
927
928
  canCreateOuting: uid ? await canRequest(uid, settings, Date.now()) : false,
929
+ canCreateReservation: uid ? await canRequest(uid, settings, Date.now()) : false,
928
930
  });
929
931
  };
930
932
 
@@ -1050,8 +1052,18 @@ api.createOuting = async function (req, res) {
1050
1052
  if (!req.uid) return res.status(401).json({ error: 'not-logged-in' });
1051
1053
 
1052
1054
  const startTs = toTs(req.body && req.body.start);
1053
- const ok = await canRequest(req.uid, settings, startTs);
1054
- if (!ok) return res.status(403).json({ error: 'not-allowed' });
1055
+ // Permissions for outings must match reservations/locations rights.
1056
+ // We intentionally base the "auto" yearly group on the *current* year,
1057
+ // not on the outing date, so members can plan future outings without
1058
+ // requiring next-year group membership.
1059
+ const ok = await canRequest(req.uid, settings, Date.now());
1060
+ if (!ok) {
1061
+ return res.status(403).json({
1062
+ error: 'not-allowed',
1063
+ code: 'NOT_MEMBER',
1064
+ message: 'Vous devez être adhérent Onekite',
1065
+ });
1066
+ }
1055
1067
 
1056
1068
  const title = String((req.body && req.body.title) || '').trim() || 'Sortie';
1057
1069
  const endTs = toTs(req.body && req.body.end);
@@ -1182,7 +1194,13 @@ api.createReservation = async function (req, res) {
1182
1194
  const settings = await meta.settings.get('calendar-onekite');
1183
1195
  const startPreview = toTs(req.body.start);
1184
1196
  const ok = await canRequest(uid, settings, startPreview);
1185
- if (!ok) return res.status(403).json({ error: 'not-allowed' });
1197
+ if (!ok) {
1198
+ return res.status(403).json({
1199
+ error: 'not-allowed',
1200
+ code: 'NOT_MEMBER',
1201
+ message: 'Vous devez être adhérent Onekite',
1202
+ });
1203
+ }
1186
1204
 
1187
1205
  const isValidator = await canValidate(uid, settings);
1188
1206
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-onekite-calendar",
3
- "version": "2.0.47",
3
+ "version": "2.0.49",
4
4
  "description": "FullCalendar-based equipment reservation workflow with admin approval & HelloAsso payment for NodeBB",
5
5
  "main": "library.js",
6
6
  "license": "MIT",
package/plugin.json CHANGED
@@ -39,5 +39,5 @@
39
39
  "acpScripts": [
40
40
  "public/admin.js"
41
41
  ],
42
- "version": "2.0.47"
42
+ "version": "2.0.49"
43
43
  }
package/public/client.js CHANGED
@@ -1203,6 +1203,8 @@ function toDatetimeLocalValue(date) {
1203
1203
  const caps = await loadCapabilities().catch(() => ({}));
1204
1204
  const canCreateSpecial = !!caps.canCreateSpecial;
1205
1205
  const canDeleteSpecial = !!caps.canDeleteSpecial;
1206
+ const canCreateOuting = !!caps.canCreateOuting;
1207
+ const canCreateReservation = !!caps.canCreateReservation;
1206
1208
 
1207
1209
  // Creation chooser: Location / Prévision de sortie / Évènement (si autorisé).
1208
1210
 
@@ -1281,6 +1283,17 @@ function toDatetimeLocalValue(date) {
1281
1283
  if (isDialogOpen) return;
1282
1284
  if (!lockAction('create', 900)) return;
1283
1285
 
1286
+ // If the user is not an Onekite member (or not logged in), do not open
1287
+ // the creation chooser at all. This avoids a confusing "not-allowed"
1288
+ // error after the user fills the form.
1289
+ try {
1290
+ if (!canCreateReservation && !canCreateOuting && !canCreateSpecial) {
1291
+ showAlert('error', 'Vous devez être adhérent Onekite');
1292
+ try { calendar.unselect(); } catch (e) {}
1293
+ return;
1294
+ }
1295
+ } catch (e) {}
1296
+
1284
1297
  // Business rule: nothing can be created in the past.
1285
1298
  try {
1286
1299
  const startDateCheck = toLocalYmd(info.start);
@@ -1325,7 +1338,7 @@ function toDatetimeLocalValue(date) {
1325
1338
  // Buttons order matters for UX: put "Annuler" at bottom-right (last).
1326
1339
  const buttons = {};
1327
1340
 
1328
- buttons.location = {
1341
+ if (canCreateReservation) buttons.location = {
1329
1342
  label: 'Location',
1330
1343
  className: 'btn-onekite-location',
1331
1344
  callback: async () => {
@@ -1364,7 +1377,7 @@ function toDatetimeLocalValue(date) {
1364
1377
  },
1365
1378
  };
1366
1379
 
1367
- buttons.outing = {
1380
+ if (canCreateOuting) buttons.outing = {
1368
1381
  label: 'Prévision de sortie',
1369
1382
  className: 'btn-onekite-outing',
1370
1383
  callback: async () => {
@@ -1494,12 +1507,13 @@ function toDatetimeLocalValue(date) {
1494
1507
  }
1495
1508
  } catch (e) {}
1496
1509
 
1497
- // IMPORTANT: align "special" event display exactly like reservation icons.
1510
+ // IMPORTANT: align "special" events and "outings" display exactly like
1511
+ // reservation icons.
1498
1512
  // We inject the clock + time range directly into the event title so FC
1499
1513
  // doesn't reserve a separate time column (which creates a leading gap).
1500
1514
  const mapped = (Array.isArray(data) ? data : []).map((ev) => {
1501
1515
  try {
1502
- if (ev && ev.extendedProps && ev.extendedProps.type === 'special' && ev.start && ev.end) {
1516
+ if (ev && ev.extendedProps && (ev.extendedProps.type === 'special' || ev.extendedProps.type === 'outing') && ev.start && ev.end) {
1503
1517
  // Force the same visual layout as reservations in month view
1504
1518
  // (avoid the "dot" layout which introduces a leading gap).
1505
1519
  ev.display = 'block';
@@ -1525,15 +1539,18 @@ function toDatetimeLocalValue(date) {
1525
1539
  }
1526
1540
  },
1527
1541
  eventDidMount: function (arg) {
1528
- // Keep special event colors consistent.
1542
+ // Keep special event + outing colors consistent even if a theme overrides
1543
+ // FullCalendar defaults.
1529
1544
  try {
1530
1545
  const ev = arg && arg.event;
1531
1546
  if (!ev) return;
1532
- if (ev.extendedProps && ev.extendedProps.type === 'special') {
1547
+ const t = ev.extendedProps && ev.extendedProps.type;
1548
+ if (t === 'special' || t === 'outing') {
1533
1549
  const el2 = arg.el;
1534
1550
  if (el2 && el2.style) {
1535
- el2.style.backgroundColor = '#8e44ad';
1536
- el2.style.borderColor = '#8e44ad';
1551
+ const bg = (t === 'outing') ? '#2980b9' : '#8e44ad';
1552
+ el2.style.backgroundColor = bg;
1553
+ el2.style.borderColor = bg;
1537
1554
  el2.style.color = '#ffffff';
1538
1555
  }
1539
1556
  }