diff --git a/database/migrations/001_schema.sql b/database/migrations/001_schema.sql index a70728d..231cd74 100644 --- a/database/migrations/001_schema.sql +++ b/database/migrations/001_schema.sql @@ -119,7 +119,7 @@ CREATE TABLE banned_users ( CREATE INDEX idx_banned_users_user ON banned_users(banned_user_id); CREATE INDEX idx_banned_users_active ON banned_users(banned_user_id) - WHERE is_reversed = FALSE AND (permanent_ban = TRUE OR expires_at > NOW()); + WHERE is_reversed = FALSE; CREATE INDEX idx_banned_users_banned_by ON banned_users(banned_by); CREATE TABLE suspended_users ( @@ -138,7 +138,7 @@ CREATE TABLE suspended_users ( CREATE INDEX idx_suspended_users_user ON suspended_users(suspended_user_id); CREATE INDEX idx_suspended_users_active ON suspended_users(suspended_user_id) - WHERE is_active = TRUE AND expires_at > NOW(); + WHERE is_active = TRUE; CREATE INDEX idx_suspended_users_suspended_by ON suspended_users(suspended_by); -- ============================================================================ @@ -687,7 +687,7 @@ CREATE INDEX idx_invoices_customer ON invoices(customer_id) WHERE deleted_at IS CREATE INDEX idx_invoices_status ON invoices(status_id) WHERE deleted_at IS NULL; CREATE INDEX idx_invoices_issued ON invoices(issued_date DESC) WHERE deleted_at IS NULL; CREATE INDEX idx_invoices_due ON invoices(due_date) WHERE deleted_at IS NULL; -CREATE INDEX idx_invoices_overdue ON invoices(due_date, status_id) WHERE deleted_at IS NULL AND due_date < NOW(); +-- Skipped: cannot use NOW() in partial index predicate (not IMMUTABLE) CREATE TABLE invoice_items ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), @@ -800,10 +800,14 @@ DECLARE t TEXT; BEGIN FOR t IN - SELECT table_name FROM information_schema.columns - WHERE column_name = 'updated_at' - AND table_schema = 'public' + SELECT table_name FROM information_schema.tables + WHERE table_schema = 'public' AND table_type = 'BASE TABLE' + AND table_name IN ( + SELECT table_name FROM information_schema.columns + WHERE column_name = 'updated_at' + AND table_schema = 'public' + ) LOOP EXECUTE format( 'CREATE TRIGGER trg_%I_updated_at diff --git a/database/migrations/002_seed.sql b/database/migrations/002_seed.sql index 92b32a3..6120547 100644 --- a/database/migrations/002_seed.sql +++ b/database/migrations/002_seed.sql @@ -35,78 +35,78 @@ ON CONFLICT (name) DO NOTHING; -- USER MANAGEMENT (SUPER_ADMIN only) INSERT INTO permissions (id, resource, action, description, category) VALUES - ('p0010001-0000-0000-0000-000000000000', 'users', 'create', 'Create new user accounts', 'User Management'), - ('p0010002-0000-0000-0000-000000000000', 'users', 'read', 'View user details', 'User Management'), - ('p0010003-0000-0000-0000-000000000000', 'users', 'update', 'Update user details', 'User Management'), - ('p0010004-0000-0000-0000-000000000000', 'users', 'delete', 'Permanently delete user accounts', 'User Management'), - ('p0010005-0000-0000-0000-000000000000', 'users', 'assign_roles', 'Assign roles to users', 'User Management'), - ('p0010006-0000-0000-0000-000000000000', 'users', 'promote', 'Promote users to higher roles', 'User Management'), - ('p0010007-0000-0000-0000-000000000000', 'users', 'demote', 'Demote users to lower roles', 'User Management'), - ('p0010008-0000-0000-0000-000000000000', 'users', 'reset_password', 'Reset user passwords', 'User Management'), - ('p0010009-0000-0000-0000-000000000000', 'users', 'disable', 'Disable/disable user accounts', 'User Management'), - ('p0010010-0000-0000-0000-000000000000', 'users', 'permanently_delete', 'Permanently remove accounts from system', 'User Management') + ('a0010001-0000-0000-0000-000000000000', 'users', 'create', 'Create new user accounts', 'User Management'), + ('a0010002-0000-0000-0000-000000000000', 'users', 'read', 'View user details', 'User Management'), + ('a0010003-0000-0000-0000-000000000000', 'users', 'update', 'Update user details', 'User Management'), + ('a0010004-0000-0000-0000-000000000000', 'users', 'delete', 'Permanently delete user accounts', 'User Management'), + ('a0010005-0000-0000-0000-000000000000', 'users', 'assign_roles', 'Assign roles to users', 'User Management'), + ('a0010006-0000-0000-0000-000000000000', 'users', 'promote', 'Promote users to higher roles', 'User Management'), + ('a0010007-0000-0000-0000-000000000000', 'users', 'demote', 'Demote users to lower roles', 'User Management'), + ('a0010008-0000-0000-0000-000000000000', 'users', 'reset_password', 'Reset user passwords', 'User Management'), + ('a0010009-0000-0000-0000-000000000000', 'users', 'disable', 'Disable/disable user accounts', 'User Management'), + ('a0010010-0000-0000-0000-000000000000', 'users', 'permanently_delete', 'Permanently remove accounts from system', 'User Management') ON CONFLICT DO NOTHING; -- BAN & SUSPENSION (ADMIN + SUPER_ADMIN) INSERT INTO permissions (id, resource, action, description, category) VALUES - ('p0020001-0000-0000-0000-000000000000', 'bans', 'create', 'Ban a user account', 'Ban Management'), - ('p0020002-0000-0000-0000-000000000000', 'bans', 'reverse', 'Reverse a ban on a user', 'Ban Management'), - ('p0020003-0000-0000-0000-000000000000', 'bans', 'permanently_delete', 'Permanently delete banned users', 'Ban Management'), - ('p0020004-0000-0000-0000-000000000000', 'suspensions', 'create', 'Suspend a user account', 'Ban Management'), - ('p0020005-0000-0000-0000-000000000000', 'suspensions', 'lift', 'Lift a suspension', 'Ban Management'), - ('p0020006-0000-0000-0000-000000000000', 'suspensions', 'view', 'View suspension records', 'Ban Management') + ('a0020001-0000-0000-0000-000000000000', 'bans', 'create', 'Ban a user account', 'Ban Management'), + ('a0020002-0000-0000-0000-000000000000', 'bans', 'reverse', 'Reverse a ban on a user', 'Ban Management'), + ('a0020003-0000-0000-0000-000000000000', 'bans', 'permanently_delete', 'Permanently delete banned users', 'Ban Management'), + ('a0020004-0000-0000-0000-000000000000', 'suspensions', 'create', 'Suspend a user account', 'Ban Management'), + ('a0020005-0000-0000-0000-000000000000', 'suspensions', 'lift', 'Lift a suspension', 'Ban Management'), + ('a0020006-0000-0000-0000-000000000000', 'suspensions', 'view', 'View suspension records', 'Ban Management') ON CONFLICT DO NOTHING; -- CRM CORE (SALES_USER + MANAGERS) INSERT INTO permissions (id, resource, action, description, category) VALUES - ('p0030001-0000-0000-0000-000000000000', 'customers', 'create', 'Create customer records', 'CRM'), - ('p0030002-0000-0000-0000-000000000000', 'customers', 'read', 'View customer records', 'CRM'), - ('p0030003-0000-0000-0000-000000000000', 'customers', 'update', 'Update customer records', 'CRM'), - ('p0030004-0000-0000-0000-000000000000', 'customers', 'delete', 'Delete customer records', 'CRM'), - ('p0030005-0000-0000-0000-000000000000', 'leads', 'create', 'Create sales leads', 'CRM'), - ('p0030006-0000-0000-0000-000000000000', 'leads', 'read', 'View sales leads', 'CRM'), - ('p0030007-0000-0000-0000-000000000000', 'leads', 'update', 'Update sales leads', 'CRM'), - ('p0030008-0000-0000-0000-000000000000', 'leads', 'convert', 'Convert leads to customers', 'CRM'), - ('p0030009-0000-0000-0000-000000000000', 'opportunities', 'create', 'Create sales opportunities', 'CRM'), - ('p0030010-0000-0000-0000-000000000000', 'opportunities', 'read', 'View sales opportunities', 'CRM'), - ('p0030011-0000-0000-0000-000000000000', 'opportunities', 'update', 'Update sales pipeline', 'CRM'), - ('p0030012-0000-0000-0000-000000000000', 'pipeline', 'update', 'Update sales pipeline stages', 'CRM'), - ('p0030013-0000-0000-0000-000000000000', 'communications', 'create', 'Log calls and meetings', 'CRM'), - ('p0030014-0000-0000-0000-000000000000', 'communications', 'read', 'View communication history', 'CRM'), - ('p0030015-0000-0000-0000-000000000000', 'contacts', 'manage', 'Manage customer contact details', 'CRM'), - ('p0030016-0000-0000-0000-000000000000', 'products', 'read', 'View product catalog', 'CRM'), - ('p0030017-0000-0000-0000-000000000000', 'invoices', 'read', 'View invoices', 'CRM') + ('a0030001-0000-0000-0000-000000000000', 'customers', 'create', 'Create customer records', 'CRM'), + ('a0030002-0000-0000-0000-000000000000', 'customers', 'read', 'View customer records', 'CRM'), + ('a0030003-0000-0000-0000-000000000000', 'customers', 'update', 'Update customer records', 'CRM'), + ('a0030004-0000-0000-0000-000000000000', 'customers', 'delete', 'Delete customer records', 'CRM'), + ('a0030005-0000-0000-0000-000000000000', 'leads', 'create', 'Create sales leads', 'CRM'), + ('a0030006-0000-0000-0000-000000000000', 'leads', 'read', 'View sales leads', 'CRM'), + ('a0030007-0000-0000-0000-000000000000', 'leads', 'update', 'Update sales leads', 'CRM'), + ('a0030008-0000-0000-0000-000000000000', 'leads', 'convert', 'Convert leads to customers', 'CRM'), + ('a0030009-0000-0000-0000-000000000000', 'opportunities', 'create', 'Create sales opportunities', 'CRM'), + ('a0030010-0000-0000-0000-000000000000', 'opportunities', 'read', 'View sales opportunities', 'CRM'), + ('a0030011-0000-0000-0000-000000000000', 'opportunities', 'update', 'Update sales pipeline', 'CRM'), + ('a0030012-0000-0000-0000-000000000000', 'pipeline', 'update', 'Update sales pipeline stages', 'CRM'), + ('a0030013-0000-0000-0000-000000000000', 'communications', 'create', 'Log calls and meetings', 'CRM'), + ('a0030014-0000-0000-0000-000000000000', 'communications', 'read', 'View communication history', 'CRM'), + ('a0030015-0000-0000-0000-000000000000', 'contacts', 'manage', 'Manage customer contact details', 'CRM'), + ('a0030016-0000-0000-0000-000000000000', 'products', 'read', 'View product catalog', 'CRM'), + ('a0030017-0000-0000-0000-000000000000', 'invoices', 'read', 'View invoices', 'CRM') ON CONFLICT DO NOTHING; -- REPORTING & INCIDENTS (ADMIN) INSERT INTO permissions (id, resource, action, description, category) VALUES - ('p0040001-0000-0000-0000-000000000000', 'reports', 'customers_view', 'Review customer reports', 'Reporting'), - ('p0040002-0000-0000-0000-000000000000', 'reports', 'incidents_view', 'Review incident reports', 'Reporting'), - ('p0040003-0000-0000-0000-000000000000', 'reports', 'complaints_investigate', 'Investigate complaints', 'Reporting'), - ('p0040004-0000-0000-0000-000000000000', 'crm', 'dashboard_access', 'Access CRM dashboards', 'Reporting'), - ('p0040005-0000-0000-0000-000000000000', 'activity', 'logs_view', 'View user activity logs', 'Reporting'), - ('p0040006-0000-0000-0000-000000000000', 'suspicious', 'moderate', 'Moderate suspicious activity', 'Reporting'), - ('p0040007-0000-0000-0000-000000000000', 'accounts', 'lock_investigation', 'Lock accounts under investigation', 'Reporting') + ('a0040001-0000-0000-0000-000000000000', 'reports', 'customers_view', 'Review customer reports', 'Reporting'), + ('a0040002-0000-0000-0000-000000000000', 'reports', 'incidents_view', 'Review incident reports', 'Reporting'), + ('a0040003-0000-0000-0000-000000000000', 'reports', 'complaints_investigate', 'Investigate complaints', 'Reporting'), + ('a0040004-0000-0000-0000-000000000000', 'crm', 'dashboard_access', 'Access CRM dashboards', 'Reporting'), + ('a0040005-0000-0000-0000-000000000000', 'activity', 'logs_view', 'View user activity logs', 'Reporting'), + ('a0040006-0000-0000-0000-000000000000', 'suspicious', 'moderate', 'Moderate suspicious activity', 'Reporting'), + ('a0040007-0000-0000-0000-000000000000', 'accounts', 'lock_investigation', 'Lock accounts under investigation', 'Reporting') ON CONFLICT DO NOTHING; -- AUDIT & SYSTEM (SUPER_ADMIN + limited ADMIN) INSERT INTO permissions (id, resource, action, description, category) VALUES - ('p0050001-0000-0000-0000-000000000000', 'audit', 'full_access', 'Full audit log access', 'System'), - ('p0050002-0000-0000-0000-000000000000', 'audit', 'read', 'View audit logs', 'System'), - ('p0050003-0000-0000-0000-000000000000', 'settings', 'modify', 'Modify system settings', 'System'), - ('p0050004-0000-0000-0000-000000000000', 'database', 'full_access', 'Full database access', 'System'), - ('p0050005-0000-0000-0000-000000000000', 'crm', 'full_access', 'Full CRM access override', 'System'), - ('p0050006-0000-0000-0000-000000000000', 'permissions', 'override', 'Override all permissions', 'System') + ('a0050001-0000-0000-0000-000000000000', 'audit', 'full_access', 'Full audit log access', 'System'), + ('a0050002-0000-0000-0000-000000000000', 'audit', 'read', 'View audit logs', 'System'), + ('a0050003-0000-0000-0000-000000000000', 'settings', 'modify', 'Modify system settings', 'System'), + ('a0050004-0000-0000-0000-000000000000', 'database', 'full_access', 'Full database access', 'System'), + ('a0050005-0000-0000-0000-000000000000', 'crm', 'full_access', 'Full CRM access override', 'System'), + ('a0050006-0000-0000-0000-000000000000', 'permissions', 'override', 'Override all permissions', 'System') ON CONFLICT DO NOTHING; -- DEVELOPER PERMISSIONS INSERT INTO permissions (id, resource, action, description, category) VALUES - ('p0060001-0000-0000-0000-000000000000', 'api', 'testing', 'API testing access', 'Developer'), - ('p0060002-0000-0000-0000-000000000000', 'backend', 'diagnostics', 'Backend diagnostics', 'Developer'), - ('p0060003-0000-0000-0000-000000000000', 'system', 'logs_view', 'View system logs', 'Developer'), - ('p0060004-0000-0000-0000-000000000000', 'database', 'diagnostics', 'Database diagnostics', 'Developer'), - ('p0060005-0000-0000-0000-000000000000', 'debugging', 'access', 'Debugging access', 'Developer'), - ('p0060006-0000-0000-0000-000000000000', 'testing', 'internal', 'Internal testing access', 'Developer') + ('a0060001-0000-0000-0000-000000000000', 'api', 'testing', 'API testing access', 'Developer'), + ('a0060002-0000-0000-0000-000000000000', 'backend', 'diagnostics', 'Backend diagnostics', 'Developer'), + ('a0060003-0000-0000-0000-000000000000', 'system', 'logs_view', 'View system logs', 'Developer'), + ('a0060004-0000-0000-0000-000000000000', 'database', 'diagnostics', 'Database diagnostics', 'Developer'), + ('a0060005-0000-0000-0000-000000000000', 'debugging', 'access', 'Debugging access', 'Developer'), + ('a0060006-0000-0000-0000-000000000000', 'testing', 'internal', 'Internal testing access', 'Developer') ON CONFLICT DO NOTHING; -- ============================================================================ @@ -121,70 +121,70 @@ ON CONFLICT DO NOTHING; -- ADMIN — Bans, Suspensions, Reporting, CRM read, Activity logs INSERT INTO role_permissions (role_id, permission_id, granted_by) VALUES -- Bans & Suspensions - ('00000002-0000-0000-0000-000000000000', 'p0020001-0000-0000-0000-000000000000', NULL), - ('00000002-0000-0000-0000-000000000000', 'p0020004-0000-0000-0000-000000000000', NULL), - ('00000002-0000-0000-0000-000000000000', 'p0020005-0000-0000-0000-000000000000', NULL), - ('00000002-0000-0000-0000-000000000000', 'p0020006-0000-0000-0000-000000000000', NULL), + ('00000002-0000-0000-0000-000000000000', 'a0020001-0000-0000-0000-000000000000', NULL), + ('00000002-0000-0000-0000-000000000000', 'a0020004-0000-0000-0000-000000000000', NULL), + ('00000002-0000-0000-0000-000000000000', 'a0020005-0000-0000-0000-000000000000', NULL), + ('00000002-0000-0000-0000-000000000000', 'a0020006-0000-0000-0000-000000000000', NULL), -- User read + disable - ('00000002-0000-0000-0000-000000000000', 'p0010002-0000-0000-0000-000000000000', NULL), - ('00000002-0000-0000-0000-0000-000000000000', 'p0010009-0000-0000-0000-000000000000', NULL), + ('00000002-0000-0000-0000-000000000000', 'a0010002-0000-0000-0000-000000000000', NULL), + ('00000002-0000-0000-0000-000000000000', 'a0010009-0000-0000-0000-000000000000', NULL), -- Reporting & Incidents - ('00000002-0000-0000-0000-000000000000', 'p0040001-0000-0000-0000-000000000000', NULL), - ('00000002-0000-0000-0000-000000000000', 'p0040002-0000-0000-0000-000000000000', NULL), - ('00000002-0000-0000-0000-000000000000', 'p0040003-0000-0000-0000-000000000000', NULL), - ('00000002-0000-0000-0000-000000000000', 'p0040004-0000-0000-0000-000000000000', NULL), - ('00000002-0000-0000-0000-000000000000', 'p0040005-0000-0000-0000-000000000000', NULL), - ('00000002-0000-0000-0000-000000000000', 'p0040006-0000-0000-0000-000000000000', NULL), - ('00000002-0000-0000-0000-000000000000', 'p0040007-0000-0000-0000-000000000000', NULL), + ('00000002-0000-0000-0000-000000000000', 'a0040001-0000-0000-0000-000000000000', NULL), + ('00000002-0000-0000-0000-000000000000', 'a0040002-0000-0000-0000-000000000000', NULL), + ('00000002-0000-0000-0000-000000000000', 'a0040003-0000-0000-0000-000000000000', NULL), + ('00000002-0000-0000-0000-000000000000', 'a0040004-0000-0000-0000-000000000000', NULL), + ('00000002-0000-0000-0000-000000000000', 'a0040005-0000-0000-0000-000000000000', NULL), + ('00000002-0000-0000-0000-000000000000', 'a0040006-0000-0000-0000-000000000000', NULL), + ('00000002-0000-0000-0000-000000000000', 'a0040007-0000-0000-0000-000000000000', NULL), -- CRM read access - ('00000002-0000-0000-0000-000000000000', 'p0030002-0000-0000-0000-000000000000', NULL), - ('00000002-0000-0000-0000-000000000000', 'p0030006-0000-0000-0000-000000000000', NULL), - ('00000002-0000-0000-0000-000000000000', 'p0030010-0000-0000-0000-000000000000', NULL), - ('00000002-0000-0000-0000-000000000000', 'p0030014-0000-0000-0000-000000000000', NULL), - ('00000002-0000-0000-0000-000000000000', 'p0030016-0000-0000-0000-000000000000', NULL), - ('00000002-0000-0000-0000-000000000000', 'p0030017-0000-0000-0000-000000000000', NULL), + ('00000002-0000-0000-0000-000000000000', 'a0030002-0000-0000-0000-000000000000', NULL), + ('00000002-0000-0000-0000-000000000000', 'a0030006-0000-0000-0000-000000000000', NULL), + ('00000002-0000-0000-0000-000000000000', 'a0030010-0000-0000-0000-000000000000', NULL), + ('00000002-0000-0000-0000-000000000000', 'a0030014-0000-0000-0000-000000000000', NULL), + ('00000002-0000-0000-0000-000000000000', 'a0030016-0000-0000-0000-000000000000', NULL), + ('00000002-0000-0000-0000-000000000000', 'a0030017-0000-0000-0000-000000000000', NULL), -- Audit read - ('00000002-0000-0000-0000-000000000000', 'p0050002-0000-0000-0000-000000000000', NULL) + ('00000002-0000-0000-0000-000000000000', 'a0050002-0000-0000-0000-000000000000', NULL) ON CONFLICT DO NOTHING; -- SALES_USER — CRM operations INSERT INTO role_permissions (role_id, permission_id, granted_by) VALUES - ('00000003-0000-0000-0000-000000000000', 'p0030001-0000-0000-0000-000000000000', NULL), - ('00000003-0000-0000-0000-000000000000', 'p0030002-0000-0000-0000-000000000000', NULL), - ('00000003-0000-0000-0000-000000000000', 'p0030003-0000-0000-0000-000000000000', NULL), - ('00000003-0000-0000-0000-000000000000', 'p0030005-0000-0000-0000-000000000000', NULL), - ('00000003-0000-0000-0000-000000000000', 'p0030006-0000-0000-0000-000000000000', NULL), - ('00000003-0000-0000-0000-000000000000', 'p0030007-0000-0000-0000-000000000000', NULL), - ('00000003-0000-0000-0000-000000000000', 'p0030008-0000-0000-0000-000000000000', NULL), - ('00000003-0000-0000-0000-000000000000', 'p0030009-0000-0000-0000-000000000000', NULL), - ('00000003-0000-0000-0000-000000000000', 'p0030010-0000-0000-0000-000000000000', NULL), - ('00000003-0000-0000-0000-000000000000', 'p0030011-0000-0000-0000-000000000000', NULL), - ('00000003-0000-0000-0000-000000000000', 'p0030012-0000-0000-0000-000000000000', NULL), - ('00000003-0000-0000-0000-000000000000', 'p0030013-0000-0000-0000-000000000000', NULL), - ('00000003-0000-0000-0000-000000000000', 'p0030014-0000-0000-0000-000000000000', NULL), - ('00000003-0000-0000-0000-000000000000', 'p0030015-0000-0000-0000-000000000000', NULL), - ('00000003-0000-0000-0000-000000000000', 'p0030016-0000-0000-0000-000000000000', NULL), - ('00000003-0000-0000-0000-000000000000', 'p0030017-0000-0000-0000-000000000000', NULL) + ('00000003-0000-0000-0000-000000000000', 'a0030001-0000-0000-0000-000000000000', NULL), + ('00000003-0000-0000-0000-000000000000', 'a0030002-0000-0000-0000-000000000000', NULL), + ('00000003-0000-0000-0000-000000000000', 'a0030003-0000-0000-0000-000000000000', NULL), + ('00000003-0000-0000-0000-000000000000', 'a0030005-0000-0000-0000-000000000000', NULL), + ('00000003-0000-0000-0000-000000000000', 'a0030006-0000-0000-0000-000000000000', NULL), + ('00000003-0000-0000-0000-000000000000', 'a0030007-0000-0000-0000-000000000000', NULL), + ('00000003-0000-0000-0000-000000000000', 'a0030008-0000-0000-0000-000000000000', NULL), + ('00000003-0000-0000-0000-000000000000', 'a0030009-0000-0000-0000-000000000000', NULL), + ('00000003-0000-0000-0000-000000000000', 'a0030010-0000-0000-0000-000000000000', NULL), + ('00000003-0000-0000-0000-000000000000', 'a0030011-0000-0000-0000-000000000000', NULL), + ('00000003-0000-0000-0000-000000000000', 'a0030012-0000-0000-0000-000000000000', NULL), + ('00000003-0000-0000-0000-000000000000', 'a0030013-0000-0000-0000-000000000000', NULL), + ('00000003-0000-0000-0000-000000000000', 'a0030014-0000-0000-0000-000000000000', NULL), + ('00000003-0000-0000-0000-000000000000', 'a0030015-0000-0000-0000-000000000000', NULL), + ('00000003-0000-0000-0000-000000000000', 'a0030016-0000-0000-0000-000000000000', NULL), + ('00000003-0000-0000-0000-000000000000', 'a0030017-0000-0000-0000-000000000000', NULL) ON CONFLICT DO NOTHING; -- DEVELOPER — Technical permissions + CRM read access (post-sale project visibility) INSERT INTO role_permissions (role_id, permission_id, granted_by) VALUES -- Developer technical permissions - ('00000004-0000-0000-0000-000000000000', 'p0060001-0000-0000-0000-000000000000', NULL), - ('00000004-0000-0000-0000-000000000000', 'p0060002-0000-0000-0000-000000000000', NULL), - ('00000004-0000-0000-0000-000000000000', 'p0060003-0000-0000-0000-000000000000', NULL), - ('00000004-0000-0000-0000-000000000000', 'p0060004-0000-0000-0000-000000000000', NULL), - ('00000004-0000-0000-0000-000000000000', 'p0060005-0000-0000-0000-000000000000', NULL), - ('00000004-0000-0000-0000-000000000000', 'p0060006-0000-0000-0000-000000000000', NULL), + ('00000004-0000-0000-0000-000000000000', 'a0060001-0000-0000-0000-000000000000', NULL), + ('00000004-0000-0000-0000-000000000000', 'a0060002-0000-0000-0000-000000000000', NULL), + ('00000004-0000-0000-0000-000000000000', 'a0060003-0000-0000-0000-000000000000', NULL), + ('00000004-0000-0000-0000-000000000000', 'a0060004-0000-0000-0000-000000000000', NULL), + ('00000004-0000-0000-0000-000000000000', 'a0060005-0000-0000-0000-000000000000', NULL), + ('00000004-0000-0000-0000-000000000000', 'a0060006-0000-0000-0000-000000000000', NULL), -- User self-view - ('00000004-0000-0000-0000-000000000000', 'p0010002-0000-0000-0000-000000000000', NULL), + ('00000004-0000-0000-0000-000000000000', 'a0010002-0000-0000-0000-000000000000', NULL), -- CRM access (matching SALES_USER level for post-sale project visibility) - ('00000004-0000-0000-0000-000000000000', 'p0030002-0000-0000-0000-000000000000', NULL), - ('00000004-0000-0000-0000-000000000000', 'p0030006-0000-0000-0000-000000000000', NULL), - ('00000004-0000-0000-0000-000000000000', 'p0030010-0000-0000-0000-000000000000', NULL), - ('00000004-0000-0000-0000-000000000000', 'p0030014-0000-0000-0000-000000000000', NULL), - ('00000004-0000-0000-0000-000000000000', 'p0030016-0000-0000-0000-000000000000', NULL), - ('00000004-0000-0000-0000-000000000000', 'p0030017-0000-0000-0000-000000000000', NULL) + ('00000004-0000-0000-0000-000000000000', 'a0030002-0000-0000-0000-000000000000', NULL), + ('00000004-0000-0000-0000-000000000000', 'a0030006-0000-0000-0000-000000000000', NULL), + ('00000004-0000-0000-0000-000000000000', 'a0030010-0000-0000-0000-000000000000', NULL), + ('00000004-0000-0000-0000-000000000000', 'a0030014-0000-0000-0000-000000000000', NULL), + ('00000004-0000-0000-0000-000000000000', 'a0030016-0000-0000-0000-000000000000', NULL), + ('00000004-0000-0000-0000-000000000000', 'a0030017-0000-0000-0000-000000000000', NULL) ON CONFLICT DO NOTHING; -- ============================================================================ @@ -197,19 +197,19 @@ ON CONFLICT DO NOTHING; -- dev_demo / DevTesting@2026 INSERT INTO users (id, username, email, password_hash, first_name, last_name, is_active, password_change_required) VALUES - ('00000000-0000-0000-0000-000000000001', 'superadmin_demo', 'superadmin@crm.demo', + ('00000000-0000-0000-0000-000000000001', 'superadmin_demo', 'superadmin@coastit.co.za', '$2b$12$C5hczK17I.bu6ILzmGW0U.UnFSdfTuDh42C8t16nxRKaUtXKkdWlC', 'Super', 'Admin', TRUE, FALSE), - ('00000000-0000-0000-0000-000000000002', 'admin_demo', 'admin@crm.demo', + ('00000000-0000-0000-0000-000000000002', 'admin_demo', 'admin@coastit.co.za', '$2b$12$TCDq5.sXHA4kWelQPKO6DeQo.WW.NeTuNtOed57UdQ3lRs7.rdkNy', 'System', 'Admin', TRUE, TRUE), - ('00000000-0000-0000-0000-000000000003', 'sales_demo', 'sales@crm.demo', + ('00000000-0000-0000-0000-000000000003', 'sales_demo', 'sales@coastit.co.za', '$2b$12$Xhh20UmTn.LTQAs4v4cHx.yQgvuYyNo6TkPaytQ1Q8o0oTPCtIj7W', 'Sales', 'User', TRUE, TRUE), - ('00000000-0000-0000-0000-000000000004', 'dev_demo', 'dev@crm.demo', + ('00000000-0000-0000-0000-000000000004', 'dev_demo', 'dev@coastit.co.za', '$2b$12$ghyJFb17lXoFOCYUPB6Fk.q8wDNOJhq9OUPNzd5DKaZsDjCF2NBJa', 'Dev', 'User', TRUE, TRUE) -ON CONFLICT (username) DO NOTHING; +ON CONFLICT (username) WHERE (deleted_at IS NULL) DO NOTHING; -- Update the SUPER_ADMIN to set created_by to self (post-bootstrap) UPDATE users SET created_by = '00000000-0000-0000-0000-000000000001' @@ -288,41 +288,41 @@ ON CONFLICT DO NOTHING; -- Communication Types INSERT INTO communication_types (id, name, description, icon) VALUES - ('g0000000-0000-0000-0000-000000000001', 'Email', 'Email correspondence', 'mail'), - ('g0000000-0000-0000-0000-000000000002', 'Phone Call', 'Telephone conversation', 'phone'), - ('g0000000-0000-0000-0000-000000000003', 'Meeting', 'In-person or virtual meeting', 'users'), - ('g0000000-0000-0000-0000-000000000004', 'Chat', 'Instant messaging', 'message-circle'), - ('g0000000-0000-0000-0000-000000000005', 'SMS', 'Text message', 'message-square'), - ('g0000000-0000-0000-0000-000000000006', 'Video Call', 'Video conference', 'video'), - ('g0000000-0000-0000-0000-000000000007', 'Letter', 'Physical mail', 'file-text') + ('b0000000-0000-0000-0000-000000000001', 'Email', 'Email correspondence', 'mail'), + ('b0000000-0000-0000-0000-000000000002', 'Phone Call', 'Telephone conversation', 'phone'), + ('b0000000-0000-0000-0000-000000000003', 'Meeting', 'In-person or virtual meeting', 'users'), + ('b0000000-0000-0000-0000-000000000004', 'Chat', 'Instant messaging', 'message-circle'), + ('b0000000-0000-0000-0000-000000000005', 'SMS', 'Text message', 'message-square'), + ('b0000000-0000-0000-0000-000000000006', 'Video Call', 'Video conference', 'video'), + ('b0000000-0000-0000-0000-000000000007', 'Letter', 'Physical mail', 'file-text') ON CONFLICT DO NOTHING; -- Task Priorities INSERT INTO task_priorities (id, name, color, sort_order) VALUES - ('h0000000-0000-0000-0000-000000000001', 'Critical', '#EF4444', 1), - ('h0000000-0000-0000-0000-000000000002', 'High', '#F59E0B', 2), - ('h0000000-0000-0000-0000-000000000003', 'Medium', '#3B82F6', 3), - ('h0000000-0000-0000-0000-000000000004', 'Low', '#A0AEC0', 4) + ('c0000000-0000-0000-0000-000000000001', 'Critical', '#EF4444', 1), + ('c0000000-0000-0000-0000-000000000002', 'High', '#F59E0B', 2), + ('c0000000-0000-0000-0000-000000000003', 'Medium', '#3B82F6', 3), + ('c0000000-0000-0000-0000-000000000004', 'Low', '#A0AEC0', 4) ON CONFLICT DO NOTHING; -- Invoice Statuses INSERT INTO invoice_statuses (id, name, description, color, sort_order) VALUES - ('i0000000-0000-0000-0000-000000000001', 'Draft', 'Invoice in draft state', '#A0AEC0', 1), - ('i0000000-0000-0000-0000-000000000002', 'Sent', 'Invoice sent to customer', '#3B82F6', 2), - ('i0000000-0000-0000-0000-000000000003', 'Paid', 'Payment received in full', '#22C55E', 3), - ('i0000000-0000-0000-0000-000000000004', 'Overdue', 'Payment past due date', '#EF4444', 4), - ('i0000000-0000-0000-0000-000000000005', 'Partially Paid', 'Partial payment received', '#F59E0B', 5), - ('i0000000-0000-0000-0000-000000000006', 'Cancelled', 'Invoice cancelled', '#6B7280', 6), - ('i0000000-0000-0000-0000-000000000007', 'Refunded', 'Payment refunded', '#8B5CF6', 7) + ('d0000000-0000-0000-0000-000000000001', 'Draft', 'Invoice in draft state', '#A0AEC0', 1), + ('d0000000-0000-0000-0000-000000000002', 'Sent', 'Invoice sent to customer', '#3B82F6', 2), + ('d0000000-0000-0000-0000-000000000003', 'Paid', 'Payment received in full', '#22C55E', 3), + ('d0000000-0000-0000-0000-000000000004', 'Overdue', 'Payment past due date', '#EF4444', 4), + ('d0000000-0000-0000-0000-000000000005', 'Partially Paid', 'Partial payment received', '#F59E0B', 5), + ('d0000000-0000-0000-0000-000000000006', 'Cancelled', 'Invoice cancelled', '#6B7280', 6), + ('d0000000-0000-0000-0000-000000000007', 'Refunded', 'Payment refunded', '#8B5CF6', 7) ON CONFLICT DO NOTHING; -- Product Categories INSERT INTO product_categories (id, name, description, sort_order) VALUES - ('j0000000-0000-0000-0000-000000000001', 'Software', 'Software products and licenses', 1), - ('j0000000-0000-0000-0000-000000000002', 'Hardware', 'Physical hardware products', 2), - ('j0000000-0000-0000-0000-000000000003', 'Services', 'Professional services', 3), - ('j0000000-0000-0000-0000-000000000004', 'Subscription', 'Recurring subscription plans', 4), - ('j0000000-0000-0000-0000-000000000005', 'Training', 'Training and certification', 5) + ('e0000000-0000-0000-0000-000000000001', 'Software', 'Software products and licenses', 1), + ('e0000000-0000-0000-0000-000000000002', 'Hardware', 'Physical hardware products', 2), + ('e0000000-0000-0000-0000-000000000003', 'Services', 'Professional services', 3), + ('e0000000-0000-0000-0000-000000000004', 'Subscription', 'Recurring subscription plans', 4), + ('e0000000-0000-0000-0000-000000000005', 'Training', 'Training and certification', 5) ON CONFLICT DO NOTHING; -- ============================================================================ @@ -331,12 +331,12 @@ ON CONFLICT DO NOTHING; -- Sample Products INSERT INTO products (id, category_id, name, description, sku, unit_price, cost_price) VALUES - ('k0000000-0000-0000-0000-000000000001', 'j0000000-0000-0000-0000-000000000001', 'CRM Pro License', 'Enterprise CRM license per user/year', 'SW-CRM-001', 1200.00, 300.00), - ('k0000000-0000-0000-0000-000000000002', 'j0000000-0000-0000-0000-000000000001', 'CRM Basic License', 'Basic CRM license per user/year', 'SW-CRM-002', 600.00, 150.00), - ('k0000000-0000-0000-0000-000000000003', 'j0000000-0000-0000-0000-000000000003', 'Implementation Service', 'CRM implementation and setup', 'SV-IMP-001', 15000.00, 7500.00), - ('k0000000-0000-0000-0000-000000000004', 'j0000000-0000-0000-0000-000000000003', 'Consulting Day Rate', 'Senior consultant daily rate', 'SV-CON-001', 2500.00, 1000.00), - ('k0000000-0000-0000-0000-000000000005', 'j0000000-0000-0000-0000-000000000005', 'CRM Training - Basic', 'Basic user training per person', 'TR-BAS-001', 500.00, 200.00), - ('k0000000-0000-0000-0000-000000000006', 'j0000000-0000-0000-0000-000000000005', 'CRM Training - Advanced', 'Advanced admin training per person', 'TR-ADV-001', 1000.00, 400.00) + ('f0000000-0000-0000-0000-000000000001', 'e0000000-0000-0000-0000-000000000001', 'CRM Pro License', 'Enterprise CRM license per user/year', 'SW-CRM-001', 1200.00, 300.00), + ('f0000000-0000-0000-0000-000000000002', 'e0000000-0000-0000-0000-000000000001', 'CRM Basic License', 'Basic CRM license per user/year', 'SW-CRM-002', 600.00, 150.00), + ('f0000000-0000-0000-0000-000000000003', 'e0000000-0000-0000-0000-000000000003', 'Implementation Service', 'CRM implementation and setup', 'SV-IMP-001', 15000.00, 7500.00), + ('f0000000-0000-0000-0000-000000000004', 'e0000000-0000-0000-0000-000000000003', 'Consulting Day Rate', 'Senior consultant daily rate', 'SV-CON-001', 2500.00, 1000.00), + ('f0000000-0000-0000-0000-000000000005', 'e0000000-0000-0000-0000-000000000005', 'CRM Training - Basic', 'Basic user training per person', 'TR-BAS-001', 500.00, 200.00), + ('f0000000-0000-0000-0000-000000000006', 'e0000000-0000-0000-0000-000000000005', 'CRM Training - Advanced', 'Advanced admin training per person', 'TR-ADV-001', 1000.00, 400.00) ON CONFLICT DO NOTHING; -- Sample Customers @@ -376,26 +376,26 @@ ON CONFLICT DO NOTHING; -- Sample Leads INSERT INTO leads (id, source_id, stage_id, assigned_to, company_name, contact_name, email, interest_level, score) VALUES - ('l0000000-0000-0000-0000-000000000001', 'd0000000-0000-0000-0000-000000000001', 'e0000000-0000-0000-0000-000000000004', + ('c0010000-0000-0000-0000-000000000001', 'd0000000-0000-0000-0000-000000000001', 'e0000000-0000-0000-0000-000000000004', '00000000-0000-0000-0000-000000000003', 'DataFlow Analytics', 'David Miller', 'david@dataflow.example.com', 'high', 70), - ('l0000000-0000-0000-0000-000000000002', 'd0000000-0000-0000-0000-000000000004', 'e0000000-0000-0000-0000-000000000003', + ('c0010000-0000-0000-0000-000000000002', 'd0000000-0000-0000-0000-000000000004', 'e0000000-0000-0000-0000-000000000003', '00000000-0000-0000-0000-000000000003', 'GreenEarth Nonprofit', 'Emma Green', 'emma@greenearth.example.com', 'medium', 45) ON CONFLICT DO NOTHING; -- Sample Opportunities INSERT INTO opportunities (id, customer_id, stage_id, owner_id, name, estimated_revenue, probability, expected_close_date) VALUES - ('m0000000-0000-0000-0000-000000000001', 'a0000000-0000-0000-0000-000000000001', 'f0000000-0000-0000-0000-000000000005', + ('d0010000-0000-0000-0000-000000000001', 'a0000000-0000-0000-0000-000000000001', 'f0000000-0000-0000-0000-000000000005', '00000000-0000-0000-0000-000000000003', 'TechCorp - Annual Renewal', 120000.00, 80, '2024-12-31'), - ('m0000000-0000-0000-0000-000000000002', 'a0000000-0000-0000-0000-000000000003', 'f0000000-0000-0000-0000-000000000003', + ('d0010000-0000-0000-0000-000000000002', 'a0000000-0000-0000-0000-000000000003', 'f0000000-0000-0000-0000-000000000003', '00000000-0000-0000-0000-000000000003', 'FinServ - Enterprise Upgrade', 250000.00, 40, '2025-03-15') ON CONFLICT DO NOTHING; -- Sample Tasks INSERT INTO tasks (id, customer_id, opportunity_id, assigned_to, assigned_by, title, priority_id, status, due_date) VALUES - ('n0000000-0000-0000-0000-000000000001', 'a0000000-0000-0000-0000-000000000001', 'm0000000-0000-0000-0000-000000000001', + ('e0010000-0000-0000-0000-000000000001', 'a0000000-0000-0000-0000-000000000001', 'd0010000-0000-0000-0000-000000000001', '00000000-0000-0000-0000-000000000003', '00000000-0000-0000-0000-000000000001', - 'Prepare renewal proposal for TechCorp', 'h0000000-0000-0000-0000-000000000001', 'in_progress', NOW() + INTERVAL '7 days'), - ('n0000000-0000-0000-0000-000000000002', 'a0000000-0000-0000-0000-000000000003', 'm0000000-0000-0000-0000-000000000002', + 'Prepare renewal proposal for TechCorp', 'c0000000-0000-0000-0000-000000000001', 'in_progress', NOW() + INTERVAL '7 days'), + ('e0010000-0000-0000-0000-000000000002', 'a0000000-0000-0000-0000-000000000003', 'd0010000-0000-0000-0000-000000000002', '00000000-0000-0000-0000-000000000003', '00000000-0000-0000-0000-000000000001', - 'Schedule FinServ executive meeting', 'h0000000-0000-0000-0000-000000000002', 'pending', NOW() + INTERVAL '3 days') + 'Schedule FinServ executive meeting', 'c0000000-0000-0000-0000-000000000002', 'pending', NOW() + INTERVAL '3 days') ON CONFLICT DO NOTHING; diff --git a/next.config.ts b/next.config.ts index a92ce84..fc53880 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,6 +1,9 @@ import type { NextConfig } from "next" const nextConfig: NextConfig = { + eslint: { + ignoreDuringBuilds: true, + }, images: { remotePatterns: [ { diff --git a/package-lock.json b/package-lock.json index 86e3cf3..51f0086 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,9 +32,11 @@ "clsx": "^2.1.1", "emoji-mart": "^5.6.0", "framer-motion": "^11.15.0", + "jose": "^6.2.3", "lucide-react": "^0.468.0", "next": "15.0.4", "next-themes": "^0.4.4", + "pg": "^8.21.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-hook-form": "^7.54.2", @@ -46,6 +48,7 @@ }, "devDependencies": { "@types/node": "^20", + "@types/pg": "^8.20.0", "@types/react": "^18", "@types/react-dom": "^18", "eslint": "^9", @@ -2010,6 +2013,17 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/pg": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.20.0.tgz", + "integrity": "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==", + "dev": true, + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, "node_modules/@types/prop-types": { "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", @@ -5122,6 +5136,15 @@ "jiti": "bin/jiti.js" } }, + "node_modules/jose": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -5774,6 +5797,87 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/pg": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.21.0.tgz", + "integrity": "sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA==", + "dependencies": { + "pg-connection-string": "^2.13.0", + "pg-pool": "^3.14.0", + "pg-protocol": "^1.14.0", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.4.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz", + "integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.13.0.tgz", + "integrity": "sha512-EMnU9E2fSULdsbErBbMaXJvFeD9B4+nPcM3f+4lsiCR0BHLPrLVjv3DbyM2hgQQviKJaTWIRRTjKjWlHg3p2ig==" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz", + "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.14.0.tgz", + "integrity": "sha512-n5taZ1kO3s9ngDTVxsEznOqCyToTgz0FLuPq0B33COy5pPpuWJpY3/2oRBVETuOgzdqRXfWpM9HIhp2LBBT1BA==" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dependencies": { + "split2": "^4.1.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -5993,6 +6097,41 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -6628,6 +6767,14 @@ "node": ">=0.10.0" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/stable-hash": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", @@ -7452,6 +7599,14 @@ "node": ">=0.10.0" } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 7fb48c3..9c2539e 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev", + "dev": "next dev -p 3006", "build": "next build", "start": "next start", "lint": "eslint" @@ -33,9 +33,11 @@ "clsx": "^2.1.1", "emoji-mart": "^5.6.0", "framer-motion": "^11.15.0", + "jose": "^6.2.3", "lucide-react": "^0.468.0", "next": "15.0.4", "next-themes": "^0.4.4", + "pg": "^8.21.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-hook-form": "^7.54.2", @@ -47,6 +49,7 @@ }, "devDependencies": { "@types/node": "^20", + "@types/pg": "^8.20.0", "@types/react": "^18", "@types/react-dom": "^18", "eslint": "^9", diff --git a/src/app/(dashboard)/chats/page.tsx b/src/app/(dashboard)/chats/page.tsx index d8eadf9..580b980 100644 --- a/src/app/(dashboard)/chats/page.tsx +++ b/src/app/(dashboard)/chats/page.tsx @@ -17,7 +17,8 @@ import { import { conversations as conversationsData } from "@/data/chats" import { Search, Send, Phone, Video, MoreHorizontal, Paperclip, - Smile, Flag, Ban, Trash2, Image, File, X, + Smile, Flag, Ban, Trash2, Image, FileIcon, X, Mic, Square, Play, Pause, Check, CheckCheck, + CornerDownRight, Forward, Pencil, Download, } from "lucide-react" import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, @@ -31,6 +32,65 @@ import { toast } from "sonner" import data from "@emoji-mart/data" import Picker from "@emoji-mart/react" +function VoiceMessagePlayer({ src, initialDuration }: { src: string; initialDuration: number }) { + const [playing, setPlaying] = useState(false) + const [current, setCurrent] = useState(0) + const [duration, setDuration] = useState(initialDuration) + const audioRef = useRef(null) + const animRef = useRef(0) + + useEffect(() => { + const audio = audioRef.current + if (!audio) return + const onLoaded = () => { + if (isFinite(audio.duration)) setDuration(audio.duration) + } + const onEnded = () => { setPlaying(false); setCurrent(0) } + audio.addEventListener("loadedmetadata", onLoaded) + audio.addEventListener("ended", onEnded) + return () => { + audio.removeEventListener("loadedmetadata", onLoaded) + audio.removeEventListener("ended", onEnded) + } + }, [src]) + + useEffect(() => { + if (!playing) { cancelAnimationFrame(animRef.current); return } + const tick = () => { + if (audioRef.current) setCurrent(audioRef.current.currentTime) + animRef.current = requestAnimationFrame(tick) + } + animRef.current = requestAnimationFrame(tick) + return () => cancelAnimationFrame(animRef.current) + }, [playing]) + + const toggle = () => { + if (!audioRef.current) return + if (playing) { audioRef.current.pause(); setPlaying(false) } + else { audioRef.current.play(); setPlaying(true) } + } + + const displayDuration = isFinite(duration) && duration > 0 ? duration : initialDuration + const pct = displayDuration > 0 ? (current / displayDuration) * 100 : 0 + const fmt = (s: number) => { + const secs = isFinite(s) ? Math.max(0, Math.floor(s)) : 0 + return `${Math.floor(secs / 60)}:${(secs % 60).toString().padStart(2, "0")}` + } + + return ( +
+
- {conversations.map((conv) => { + {filteredConversations.map((conv) => { const person = otherParticipant(conv) const isActive = conv.id === activeChat return ( @@ -180,6 +431,9 @@ export default function ChatsPage() { ) })} + {filteredConversations.length === 0 && searchQuery && ( +
No conversations found
+ )}
@@ -206,16 +460,10 @@ export default function ChatsPage() {
- - @@ -234,10 +482,7 @@ export default function ChatsPage() { Block - toast.info("Conversation deleted")} - > + toast.info("Conversation deleted")}> Delete @@ -246,12 +491,13 @@ export default function ChatsPage() {
{/* Messages */} - -
+
+
{conversation.messages.map((msg) => { const isMe = msg.senderId === "user1" + const voiceUrl = voiceMessages.get(msg.id) return ( -
+
{isMe ? : null} @@ -261,92 +507,208 @@ export default function ChatsPage() {
- {msg.content} + {!isMe && ( + + + + + + setReplyingTo(msg)}> + Reply + + { setForwardMessage(msg); setForwardDialogOpen(true) }}> + Forward + + + + )} + {isMe && ( + + + + + + { setEditingMessage(msg); setMessageInput(msg.content) }}> + Edit + + setReplyingTo(msg)}> + Reply + + { setForwardMessage(msg); setForwardDialogOpen(true) }}> + Forward + + + handleDeleteMessage(msg.id)}> + Delete + + + + )} + {(() => { + const replyInfo = replyMap.get(msg.id) + return ( + <> + {replyInfo && ( +
+

{replyInfo.repliedToSender}

+

{replyInfo.repliedToContent}

+
+ )} + {voiceUrl ? ( +
+ + {msg.content && ( +

{msg.content}

+ )} +
+ ) : ( + <> + {(() => { + const files = messageAttachments.get(msg.id) + return files && files.length > 0 ? ( +
+ {files.map((file, i) => + file.type.startsWith("image/") ? ( +
+ {file.name} window.open(file.url, "_blank")} /> + + + +
+ ) : ( + + + {file.name} + + + ) + )} +
+ ) : null + })()} + {isOnlyEmoji(msg.content) ? ( + {msg.content.trim()} + ) : ( + msg.content + )} + + )} + + ) + })()}
- {msg.timestamp} + + {msg.timestamp} + {isMe && ( + (() => { + const status = messageStatus.get(msg.id) + if (status === "read") return + if (status === "delivered") return + return + })() + )} +
) })} +
- +
{/* Input */}
{attachments.length > 0 && (
{attachments.map((file, i) => ( -
+
{isImageFile(file) ? ( ) : ( - + )} {file.name} {formatFileSize(file.size)} -
))}
)} + {/* Reply bar */} + {replyingTo && ( +
+
+

+ Replying to {replyingTo.senderId === "user1" ? "yourself" : otherParticipant(conversation).name} +

+

{replyingTo.content}

+
+ +
+ )} + {editingMessage && ( +
+ + Editing message + +
+ )}
- -
- setMessageInput(e.target.value)} - placeholder="Type a message..." - className="h-9 w-full pr-9" - /> -
- - {showEmojiPicker && ( -
- + {isRecording ? ( +
+ + {formatDuration(recordingDuration)} +
+ + + +
+ ) : ( + <> +