feat(admin): single Sales role, welcome-email password setup, Director=sales
- Collapse the two sales roles in the create-user dropdown to one "Sales" (sales_manager relabelled). Hide super_admin + sales_agent from selection via NON_ASSIGNABLE_ROLE_NAMES; the form keeps a user's *current* role even if hidden so existing assignments stay editable. - Director becomes a senior-title twin of Sales: DIRECTOR_PERMISSIONS now equals SALES_MANAGER_PERMISSIONS (no admin/settings — Super-Admin only). Migration 0097 updates the existing global director row (idempotent, data-only; 0 users assigned on prod, so no blast radius). - Admin create-user defaults to emailing a set-password link instead of an inline password (manual entry still available via a toggle). createUserSchema: password optional + sendSetupEmail; createUser provisions with a throwaway password then triggers the set-password email. - New users get a dedicated, unique WELCOME email (crmWelcomeEmail), not the self-service "reset your password" email. A pending-welcome flag routes the shared better-auth sendResetPassword callback via account-setup-email.ts. - Phone confirmed already optional for staff accounts (no change needed). Tests: +welcome-routing, +create-user-setup; permission-matrix director block realigned to no-admin. 1662 vitest pass; tsc + eslint clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
34
tests/unit/email/crm-welcome-email.test.ts
Normal file
34
tests/unit/email/crm-welcome-email.test.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
import { crmWelcomeEmail } from '@/lib/email/templates/crm-welcome';
|
||||
|
||||
describe('crmWelcomeEmail', () => {
|
||||
it('is a unique welcome email (not a password-reset) carrying the set-password link', async () => {
|
||||
const link = 'https://crm.example.com/set-password#token=abc123';
|
||||
const { subject, html, text } = await crmWelcomeEmail({
|
||||
link,
|
||||
recipientName: 'Jane Doe',
|
||||
appName: 'Port Nimara CRM',
|
||||
});
|
||||
|
||||
// Distinct welcome framing, not the reset-password copy.
|
||||
expect(subject.toLowerCase()).toContain('welcome');
|
||||
expect(subject.toLowerCase()).not.toContain('reset');
|
||||
// No accidental double "CRM CRM" when the app name already carries it.
|
||||
expect(subject).not.toContain('CRM CRM');
|
||||
|
||||
// Greets the recipient and drives them to set their password.
|
||||
expect(html).toContain('Jane Doe');
|
||||
expect(html).toContain('set-password');
|
||||
expect(html).toContain('abc123');
|
||||
expect(text).toContain(link);
|
||||
});
|
||||
|
||||
it('falls back to a generic greeting when no name is given', async () => {
|
||||
const { html } = await crmWelcomeEmail({
|
||||
link: 'https://crm.example.com/set-password#token=xyz',
|
||||
appName: 'Port Nimara CRM',
|
||||
});
|
||||
expect(html.toLowerCase()).toContain('welcome');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user