fix: require feedback on post rejection, post-specific review text, show superadmins in team list
All checks were successful
Deploy / deploy (push) Successful in 11s

- Reject requires feedback on both client and server (400 if empty)
- PublicPostReview uses post-specific i18n keys instead of artefact ones
- Team list always includes superadmins/managers for non-superadmin users

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
fahed
2026-03-05 15:24:48 +03:00
parent 0e948cbf37
commit 93956ff117
4 changed files with 27 additions and 6 deletions

View File

@@ -902,5 +902,11 @@
"posts.submitForReview": "إرسال للمراجعة", "posts.submitForReview": "إرسال للمراجعة",
"posts.schedulePost": "جدولة المنشور", "posts.schedulePost": "جدولة المنشور",
"review.postReview": "مراجعة المنشور", "review.postReview": "مراجعة المنشور",
"review.createdBy": "أنشئ بواسطة" "review.createdBy": "أنشئ بواسطة",
"review.confirmApprovePost": "الموافقة على هذا المنشور؟",
"review.confirmRejectPost": "رفض هذا المنشور؟",
"review.confirmApprovePostDesc": "هل أنت متأكد من الموافقة على هذا المنشور؟",
"review.confirmRejectPostDesc": "هل أنت متأكد من رفض هذا المنشور؟ يرجى تقديم ملاحظات توضح السبب.",
"review.feedbackRequired": "الملاحظات (مطلوبة)",
"review.feedbackRequiredError": "يرجى تقديم ملاحظات عند الرفض"
} }

View File

@@ -902,5 +902,11 @@
"posts.submitForReview": "Submit for Review", "posts.submitForReview": "Submit for Review",
"posts.schedulePost": "Schedule Post", "posts.schedulePost": "Schedule Post",
"review.postReview": "Post Review", "review.postReview": "Post Review",
"review.createdBy": "Created by" "review.createdBy": "Created by",
"review.confirmApprovePost": "Approve this post?",
"review.confirmRejectPost": "Reject this post?",
"review.confirmApprovePostDesc": "Are you sure you want to approve this post?",
"review.confirmRejectPostDesc": "Are you sure you want to reject this post? Please provide feedback explaining why.",
"review.feedbackRequired": "Feedback (required)",
"review.feedbackRequiredError": "Please provide feedback when rejecting"
} }

View File

@@ -46,6 +46,10 @@ export default function PublicPostReview() {
toast.error(t('review.enterName')) toast.error(t('review.enterName'))
return return
} }
if (action === 'reject' && !feedback.trim()) {
toast.error(t('review.feedbackRequiredError'))
return
}
setPendingAction(action) setPendingAction(action)
} }
@@ -276,7 +280,7 @@ export default function PublicPostReview() {
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-text-primary mb-1">{t('review.feedbackOptional')}</label> <label className="block text-sm font-medium text-text-primary mb-1">{t('review.feedbackRequired')}</label>
<textarea value={feedback} onChange={e => setFeedback(e.target.value)} rows={4} <textarea value={feedback} onChange={e => setFeedback(e.target.value)} rows={4}
placeholder={t('review.feedbackPlaceholder')} placeholder={t('review.feedbackPlaceholder')}
className="w-full px-4 py-2 text-sm border border-border rounded-lg bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-brand-primary/20 focus:border-brand-primary" /> className="w-full px-4 py-2 text-sm border border-border rounded-lg bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-brand-primary/20 focus:border-brand-primary" />
@@ -328,13 +332,13 @@ export default function PublicPostReview() {
<Modal <Modal
isOpen={!!pendingAction} isOpen={!!pendingAction}
onClose={() => setPendingAction(null)} onClose={() => setPendingAction(null)}
title={pendingAction === 'approve' ? t('review.confirmApprove') : t('review.confirmReject')} title={pendingAction === 'approve' ? t('review.confirmApprovePost') : t('review.confirmRejectPost')}
isConfirm isConfirm
danger={pendingAction === 'reject'} danger={pendingAction === 'reject'}
onConfirm={() => { const a = pendingAction; setPendingAction(null); executeAction(a) }} onConfirm={() => { const a = pendingAction; setPendingAction(null); executeAction(a) }}
confirmText={pendingAction === 'approve' ? t('review.approve') : t('review.reject')} confirmText={pendingAction === 'approve' ? t('review.approve') : t('review.reject')}
> >
{pendingAction === 'approve' ? t('review.confirmApproveDesc') : t('review.confirmRejectDesc')} {pendingAction === 'approve' ? t('review.confirmApprovePostDesc') : t('review.confirmRejectPostDesc')}
</Modal> </Modal>
</div> </div>
) )

View File

@@ -898,9 +898,11 @@ app.get('/api/users/team', requireAuth, async (req, res) => {
try { myBrands = JSON.parse(currentUser?.brands || '[]'); } catch (err) { console.error('Parse user brands:', err.message); } try { myBrands = JSON.parse(currentUser?.brands || '[]'); } catch (err) { console.error('Parse user brands:', err.message); }
filtered = users.filter(u => { filtered = users.filter(u => {
// Always include self, superadmins, and managers
if (u.Id === req.session.userId || u.role === 'superadmin' || u.role === 'manager') return true;
let theirBrands = []; let theirBrands = [];
try { theirBrands = JSON.parse(u.brands || '[]'); } catch (err) { console.error('Parse team brands:', err.message); } try { theirBrands = JSON.parse(u.brands || '[]'); } catch (err) { console.error('Parse team brands:', err.message); }
return u.Id === req.session.userId || theirBrands.some(b => myBrands.includes(b)); return theirBrands.some(b => myBrands.includes(b));
}); });
} }
@@ -1586,6 +1588,9 @@ app.post('/api/public/review-post/:token/approve', async (req, res) => {
// Public: Reject post // Public: Reject post
app.post('/api/public/review-post/:token/reject', async (req, res) => { app.post('/api/public/review-post/:token/reject', async (req, res) => {
const { approved_by_name, feedback } = req.body; const { approved_by_name, feedback } = req.body;
if (!feedback || !feedback.trim()) {
return res.status(400).json({ error: 'Feedback is required when rejecting' });
}
try { try {
const posts = await nocodb.list('Posts', { const posts = await nocodb.list('Posts', {
where: `(approval_token,eq,${sanitizeWhereValue(req.params.token)})`, where: `(approval_token,eq,${sanitizeWhereValue(req.params.token)})`,