Add feature requests page with upvotes and admin status workflow
Validate / validate (push) Successful in 22s
Validate / validate (push) Successful in 22s
- New /feature-requests route accessible to all logged-in users via sidebar nav - feature_requests + feature_request_votes tables (one vote per user per request) - Submit form (modal), upvote toggle, filter by status, sort by votes/newest - System admins can change status (open / in_review / waiting_for_checks / in_progress / resolved / closed) with optional note - Submitter auto-votes their own request on creation - Admin or original submitter can delete a request Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -609,6 +609,51 @@ export const shippingAccounts = pgTable(
|
||||
(table) => [uniqueIndex('shipping_accounts_company_carrier_idx').on(table.companyId, table.carrier)]
|
||||
);
|
||||
|
||||
// ── Feature Requests ───────────────────────────────────
|
||||
|
||||
export const featureRequestStatusEnum = pgEnum('feature_request_status', [
|
||||
'open',
|
||||
'in_review',
|
||||
'waiting_for_checks',
|
||||
'in_progress',
|
||||
'resolved',
|
||||
'closed'
|
||||
]);
|
||||
|
||||
export const featureRequests = pgTable(
|
||||
'feature_requests',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
title: text('title').notNull(),
|
||||
description: text('description'),
|
||||
status: featureRequestStatusEnum('status').notNull().default('open'),
|
||||
submittedBy: text('submitted_by')
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: 'set null' }),
|
||||
statusChangedBy: text('status_changed_by').references(() => users.id, { onDelete: 'set null' }),
|
||||
statusChangedAt: timestamp('status_changed_at', { withTimezone: true }),
|
||||
statusNote: text('status_note'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow()
|
||||
},
|
||||
(table) => [index('feature_requests_status_idx').on(table.status)]
|
||||
);
|
||||
|
||||
export const featureRequestVotes = pgTable(
|
||||
'feature_request_votes',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
requestId: uuid('request_id')
|
||||
.notNull()
|
||||
.references(() => featureRequests.id, { onDelete: 'cascade' }),
|
||||
userId: text('user_id')
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: 'cascade' }),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow()
|
||||
},
|
||||
(table) => [uniqueIndex('feature_request_votes_request_user_idx').on(table.requestId, table.userId)]
|
||||
);
|
||||
|
||||
// ── Company Log (Audit Trail) ──────────────────────────
|
||||
|
||||
export const companyLogEventEnum = pgEnum('company_log_event', [
|
||||
|
||||
Reference in New Issue
Block a user