feat(events): default Guest Feedback ON via admin setting (#520)

@Rekoo-PS asked for an admin-level switch so new events can have Guest
Feedback enabled out of the box instead of toggling it on every time.
Mirrors the existing event_default_require_password pattern (#317) —
same shape end-to-end, same set of five files.

- publicSettings.js: whitelist + expose event_default_feedback_enabled
  (defaults to false to match the prior hard-coded form default; no
  behaviour change for existing installs until an admin flips it).
- adminEvents.js: rename `feedback_enabled = false` destructure to
  `feedback_enabled: feedbackEnabledInput` so we can distinguish
  "omitted" from "explicit false", then resolve the default from the
  setting only when the caller omitted it — identical to the
  require_password handling a few lines above.
- Frontend EventSettings type + state + loader: new boolean,
  default false.
- EventsTab: toggle UI right under "Require password by default".
- CreateEventPage: one-shot useEffect that seeds
  feedback_settings.feedback_enabled from the public setting on first
  load (mirrors the require_password seed effect right above it).
  Sub-toggles (likes / ratings / comments) keep their hard-coded
  true defaults so flipping the master setting immediately gives
  sensible behaviour without a second admin setting to manage.

Refs: #520
This commit is contained in:
Paul Nothaft
2026-05-18 21:26:45 +02:00
parent d44e1adba7
commit 3465b55abc
6 changed files with 59 additions and 1 deletions
+12 -1
View File
@@ -439,7 +439,7 @@ router.post('/', adminAuth, requirePermission('events.create'), [
allow_presigned_download = false,
require_password: requirePasswordInput,
// Feedback settings
feedback_enabled = false,
feedback_enabled: feedbackEnabledInput,
allow_ratings = true,
allow_likes = true,
allow_comments = true,
@@ -507,6 +507,17 @@ router.post('/', adminAuth, requirePermission('events.create'), [
}
const requirePassword = parseBooleanInput(requirePasswordInput, requirePasswordFallback);
// Default feedback_enabled from global "event_default_feedback_enabled"
// setting when the body omits it (#520 — same pattern as require_password
// above, lets admins make Guest Feedback ON the out-of-box default for
// new events instead of toggling it on every time).
let feedbackEnabledFallback = false;
if (feedbackEnabledInput === undefined) {
const setting = await readBooleanSetting('event_default_feedback_enabled');
if (setting !== undefined) feedbackEnabledFallback = setting;
}
const feedback_enabled = parseBooleanInput(feedbackEnabledInput, feedbackEnabledFallback);
// Debug logging
logger.debug('Download control values', {
allow_downloads,
+5
View File
@@ -16,6 +16,7 @@ router.get('/', async (req, res) => {
.orWhereIn('setting_key', [
'seo_meta_noindex', 'seo_meta_nofollow', 'seo_meta_noai',
'event_default_require_password',
'event_default_feedback_enabled',
'gallery_show_filter_bar',
'event_phone_field_enabled'
]);
@@ -120,6 +121,10 @@ router.get('/', async (req, res) => {
event_require_expiration: settingsObject.event_require_expiration !== false,
// Default value for "Require password" toggle in event creation form
event_default_require_password: settingsObject.event_default_require_password !== false,
// Default value for the "Guest Feedback enabled" toggle (#520).
// Defaults to false (matches the prior hard-coded form default), so
// existing installs see no behaviour change until an admin flips it.
event_default_feedback_enabled: settingsObject.event_default_feedback_enabled === true,
// Phone-number field on events is opt-in (#322).
event_phone_field_enabled: settingsObject.event_phone_field_enabled === true,
// Whether to show the search/sort filter bar in public galleries (default: true)
@@ -54,6 +54,7 @@ export interface EventSettings {
event_require_event_date: boolean;
event_require_expiration: boolean;
event_default_require_password: boolean;
event_default_feedback_enabled: boolean;
gallery_show_filter_bar: boolean;
event_phone_field_enabled: boolean;
}
@@ -133,6 +134,7 @@ export function useSettingsState() {
event_require_event_date: true,
event_require_expiration: true,
event_default_require_password: true,
event_default_feedback_enabled: false,
gallery_show_filter_bar: true,
event_phone_field_enabled: false
});
@@ -220,6 +222,7 @@ export function useSettingsState() {
event_require_event_date: toBoolean(settings.event_require_event_date, true),
event_require_expiration: toBoolean(settings.event_require_expiration, true),
event_default_require_password: toBoolean(settings.event_default_require_password, true),
event_default_feedback_enabled: toBoolean(settings.event_default_feedback_enabled, false),
gallery_show_filter_bar: toBoolean(settings.gallery_show_filter_bar, true),
event_phone_field_enabled: toBoolean(settings.event_phone_field_enabled, false)
});
@@ -169,6 +169,25 @@ export const EventsTab: React.FC<EventsTabProps> = ({
</label>
</div>
<div>
<label className="flex items-start gap-3">
<input
type="checkbox"
checked={eventSettings.event_default_feedback_enabled}
onChange={(e) => setEventSettings(prev => ({ ...prev, event_default_feedback_enabled: e.target.checked }))}
className="mt-1 w-4 h-4 text-primary-600 rounded focus:ring-primary-500"
/>
<div>
<span className="text-sm font-medium text-neutral-700 dark:text-neutral-300">
{t('settings.events.defaultFeedbackEnabled', 'Enable Guest Feedback by default')}
</span>
<p className="text-xs text-neutral-500 dark:text-neutral-400 mt-1">
{t('settings.events.defaultFeedbackEnabledHelp', 'Pre-check "Guest Feedback" when creating new events. Individual feedback options (likes, ratings, comments) can still be customised per event.')}
</p>
</div>
</label>
</div>
<div>
<label className="flex items-start gap-3">
<input
@@ -246,6 +246,25 @@ export const CreateEventPage: React.FC = () => {
}));
}, [publicSettings]);
// Honour the global "Enable Guest Feedback by default" admin setting (#520).
// Same one-shot apply pattern as require_password above — only seeds the
// master toggle. The sub-toggles (likes / ratings / comments) keep their
// hard-coded true defaults so a flipped master immediately gives sensible
// behaviour without a second admin setting to manage.
const feedbackEnabledDefaultApplied = useRef(false);
useEffect(() => {
if (feedbackEnabledDefaultApplied.current) return;
if (publicSettings?.event_default_feedback_enabled === undefined) return;
feedbackEnabledDefaultApplied.current = true;
setFormData(prev => ({
...prev,
feedback_settings: {
...prev.feedback_settings,
feedback_enabled: publicSettings.event_default_feedback_enabled === true
}
}));
}, [publicSettings]);
// Apply the global Branding default theme on first load so admins who set a
// site-wide default in Branding actually see it on new events (#323).
// This is the "always inherit colours from Branding" guarantee — every new
@@ -62,6 +62,7 @@ export interface PublicSettings {
event_require_event_date?: boolean;
event_require_expiration?: boolean;
event_default_require_password?: boolean;
event_default_feedback_enabled?: boolean;
gallery_show_filter_bar?: boolean;
event_phone_field_enabled?: boolean;
// SEO meta tags (consumed by RobotsMetaTags)