feat: observer team tab, admin-controlled applicant feedback visibility
All checks were successful
Build and Push Docker Image / build (push) Successful in 9m13s
All checks were successful
Build and Push Docker Image / build (push) Successful in 9m13s
- Add Team tab to observer project detail (configurable via admin settings) - Move applicant jury feedback visibility from per-round config to admin settings - Add per-round-type controls: evaluation, live final, deliberation - Support anonymous LiveVote and DeliberationVote display for applicants - Add fine-grained toggles: scores, criteria, written feedback, hide from rejected - Backwards compatible: falls back to old per-round config if admin settings not set - New admin settings section under Analytics tab with all visibility controls - Seed new SystemSettings keys for observer/applicant visibility Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -25,7 +25,7 @@ function categorizeModel(modelId: string): string {
|
||||
return 'other'
|
||||
}
|
||||
|
||||
function inferSettingCategory(key: string): 'AI' | 'BRANDING' | 'EMAIL' | 'STORAGE' | 'SECURITY' | 'DEFAULTS' | 'WHATSAPP' | 'FEATURE_FLAGS' {
|
||||
function inferSettingCategory(key: string): 'AI' | 'BRANDING' | 'EMAIL' | 'STORAGE' | 'SECURITY' | 'DEFAULTS' | 'WHATSAPP' | 'FEATURE_FLAGS' | 'ANALYTICS' {
|
||||
if (key.startsWith('openai') || key.startsWith('ai_') || key.startsWith('anthropic')) return 'AI'
|
||||
if (key.startsWith('smtp_') || key.startsWith('email_')) return 'EMAIL'
|
||||
if (key.startsWith('storage_') || key.startsWith('local_storage') || key.startsWith('max_file') || key.startsWith('avatar_') || key.startsWith('allowed_file')) return 'STORAGE'
|
||||
@@ -33,6 +33,7 @@ function inferSettingCategory(key: string): 'AI' | 'BRANDING' | 'EMAIL' | 'STORA
|
||||
if (key.startsWith('whatsapp_')) return 'WHATSAPP'
|
||||
if (key.startsWith('security_') || key.startsWith('session_')) return 'SECURITY'
|
||||
if (key.startsWith('learning_hub_') || key.startsWith('jury_compare_') || key.startsWith('support_')) return 'FEATURE_FLAGS'
|
||||
if (key.startsWith('applicant_') || key.startsWith('observer_') || key.startsWith('analytics_')) return 'ANALYTICS'
|
||||
return 'DEFAULTS'
|
||||
}
|
||||
|
||||
@@ -42,34 +43,40 @@ export const settingsRouter = router({
|
||||
* These are non-sensitive settings that can be exposed to any user
|
||||
*/
|
||||
getFeatureFlags: protectedProcedure.query(async ({ ctx }) => {
|
||||
const [whatsappEnabled, juryCompareEnabled, learningHubExternal, learningHubExternalUrl, supportEmail, accountReminderDays] = await Promise.all([
|
||||
ctx.prisma.systemSettings.findUnique({
|
||||
where: { key: 'whatsapp_enabled' },
|
||||
}),
|
||||
ctx.prisma.systemSettings.findUnique({
|
||||
where: { key: 'jury_compare_enabled' },
|
||||
}),
|
||||
ctx.prisma.systemSettings.findUnique({
|
||||
where: { key: 'learning_hub_external' },
|
||||
}),
|
||||
ctx.prisma.systemSettings.findUnique({
|
||||
where: { key: 'learning_hub_external_url' },
|
||||
}),
|
||||
ctx.prisma.systemSettings.findUnique({
|
||||
where: { key: 'support_email' },
|
||||
}),
|
||||
ctx.prisma.systemSettings.findUnique({
|
||||
where: { key: 'account_reminder_days' },
|
||||
}),
|
||||
])
|
||||
const keys = [
|
||||
'whatsapp_enabled', 'jury_compare_enabled', 'learning_hub_external',
|
||||
'learning_hub_external_url', 'support_email', 'account_reminder_days',
|
||||
'observer_show_team_tab',
|
||||
'applicant_show_evaluation_feedback', 'applicant_show_evaluation_scores',
|
||||
'applicant_show_evaluation_criteria', 'applicant_show_evaluation_text',
|
||||
'applicant_show_livefinal_feedback', 'applicant_show_livefinal_scores',
|
||||
'applicant_show_deliberation_feedback',
|
||||
'applicant_hide_feedback_from_rejected',
|
||||
]
|
||||
const settings = await ctx.prisma.systemSettings.findMany({
|
||||
where: { key: { in: keys } },
|
||||
})
|
||||
const map = new Map(settings.map((s) => [s.key, s.value]))
|
||||
const flag = (k: string, def = 'false') => (map.get(k) ?? def) === 'true'
|
||||
|
||||
return {
|
||||
whatsappEnabled: whatsappEnabled?.value === 'true',
|
||||
juryCompareEnabled: juryCompareEnabled?.value === 'true',
|
||||
learningHubExternal: learningHubExternal?.value === 'true',
|
||||
learningHubExternalUrl: learningHubExternalUrl?.value || '',
|
||||
supportEmail: supportEmail?.value || '',
|
||||
accountReminderDays: parseInt(accountReminderDays?.value || '3', 10),
|
||||
whatsappEnabled: flag('whatsapp_enabled'),
|
||||
juryCompareEnabled: flag('jury_compare_enabled'),
|
||||
learningHubExternal: flag('learning_hub_external'),
|
||||
learningHubExternalUrl: map.get('learning_hub_external_url') || '',
|
||||
supportEmail: map.get('support_email') || '',
|
||||
accountReminderDays: parseInt(map.get('account_reminder_days') || '3', 10),
|
||||
observerShowTeamTab: flag('observer_show_team_tab', 'true'),
|
||||
applicantFeedback: {
|
||||
evaluationEnabled: flag('applicant_show_evaluation_feedback'),
|
||||
evaluationShowScores: flag('applicant_show_evaluation_scores'),
|
||||
evaluationShowCriteria: flag('applicant_show_evaluation_criteria'),
|
||||
evaluationShowText: flag('applicant_show_evaluation_text'),
|
||||
liveFinalEnabled: flag('applicant_show_livefinal_feedback'),
|
||||
liveFinalShowScores: flag('applicant_show_livefinal_scores'),
|
||||
deliberationEnabled: flag('applicant_show_deliberation_feedback'),
|
||||
hideFromRejected: flag('applicant_hide_feedback_from_rejected'),
|
||||
},
|
||||
}
|
||||
}),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user