feat(07-06): settings-api client + SmtpSettingsForm + SMTP page (DKV-05)
Tessera CI/CD / Build & Deploy (push) Blocked by required conditions
Tessera CI/CD / Lint & Type Check (push) Successful in 41s
Tessera CI/CD / Tests (push) Waiting to run

- settings-api.ts: fetchSmtp/saveSmtp/testSmtp with credentials:'include'; SmtpConfig exposes only hasPassword (T-07-17)
- smtp-settings-form.tsx: Surface C form with show/hide password toggle, test-feedback auto-clears 6s
- settings/general/smtp/page.tsx: SmtpSettingsPage heading + SmtpSettingsForm
- de.json + en.json: add smtp.showPassword/hidePassword/testTesting/testSuccess/testFailed keys
- TDD GREEN: 5/5 tests pass; aria-hidden* on required markers removed for correct getByLabelText matching
This commit is contained in:
2026-06-27 17:03:57 +02:00
parent a96d79ad52
commit fd2dda69cb
5 changed files with 464 additions and 2 deletions
+89
View File
@@ -0,0 +1,89 @@
/**
* Settings API client — SMTP configuration.
* Consumes the SettingsController built in 07-03.
* All calls use credentials: 'include' for cookie-based auth.
*
* T-07-17: SmtpConfig exposes only hasPassword, never the secret.
* The password field is never pre-filled from server data.
*/
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001';
// --- Types mirroring the 07-03 backend contract ---
/**
* SMTP configuration as returned by GET /settings/smtp.
* encryptedPassword is NEVER returned (T-07-17).
*/
export interface SmtpConfig {
host: string;
port: number;
encryption: 'none' | 'starttls' | 'ssl-tls';
username?: string;
fromAddress: string;
/** True when an encrypted password is stored; the password value is never exposed. */
hasPassword: boolean;
}
/**
* Payload for PUT /settings/smtp and POST /settings/smtp/test.
* password is optional — omit to preserve the existing stored password.
*/
export interface SaveSmtpPayload {
host: string;
port: number;
encryption: 'none' | 'starttls' | 'ssl-tls';
username?: string;
/** Only include when the user has typed a new password. */
password?: string;
fromAddress: string;
}
// --- API functions ---
/**
* Fetch the SMTP configuration for the current tenant.
* GET /settings/smtp
* Returns null when no configuration has been saved yet (404).
*/
export async function fetchSmtp(): Promise<SmtpConfig | null> {
const res = await fetch(`${API_URL}/settings/smtp`, {
credentials: 'include',
});
if (res.status === 404) return null;
if (!res.ok) throw new Error('Failed to fetch SMTP config');
return res.json();
}
/**
* Save the SMTP configuration.
* PUT /settings/smtp
*/
export async function saveSmtp(payload: SaveSmtpPayload): Promise<SmtpConfig> {
const res = await fetch(`${API_URL}/settings/smtp`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify(payload),
});
if (!res.ok) throw new Error('Failed to save SMTP config');
return res.json();
}
/**
* Test the SMTP connection with the given settings.
* POST /settings/smtp/test
* Returns { success: boolean } — backend runs nodemailer.verify().
*/
export async function testSmtp(
payload: SaveSmtpPayload,
): Promise<{ success: boolean }> {
const res = await fetch(`${API_URL}/settings/smtp/test`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify(payload),
});
if (!res.ok) throw new Error('Failed to test SMTP config');
return res.json();
}