feat(brand): rebrand to Hedena Gold and bring main user-app chrome

Rename the brand, ship Hedena logos, persist login OTP text in the SMS list, and port home/nav/auth/theme polish from main while keeping expert customers intact.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
mahmood
2026-10-27 16:40:54 +03:30
co-authored by Cursor
parent bc881a0a8a
commit c438ab2159
94 changed files with 3385 additions and 1396 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ import { envOr } from './env';
* docs/BRANDING.md for the full list.
*/
export function brandName(): string {
return envOr('BRAND_NAME', 'تهران تاج');
return envOr('BRAND_NAME', 'هدناگلد');
}
export function brandSiteUrl(): string {
@@ -11,7 +11,7 @@ import {
UnauthorizedException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { AdminRole, CustomerKind, MessageAudience, Prisma, VerificationStep } from '@prisma/client';
import { AdminRole, CustomerKind, Prisma, VerificationStep } from '@prisma/client';
import * as bcrypt from 'bcryptjs';
import { PrismaService } from '../../prisma/prisma.service';
import {
@@ -887,10 +887,9 @@ export class CustomerAuthService {
`رمز عبور یکبار مصرف شما جهت ورود به اپلیکیشن ${brandName()} ${code} می باشد\n` +
`aMo6iFyUQaY`;
// persist:false — the real text carries the live code, and the admin SMS
// grid lists and searches `body` (SEC-11). The redacted audit row below
// takes its place.
const result = await this.sms.send(mobile, smsText, { persist: false });
// Persist the real OTP body so the admin SMS list can show login codes
// (owner request: operators need to read them; risk accepted).
const result = await this.sms.send(mobile, smsText);
// Only outside production, and only when no gateway is configured, so a
// deployment can never print live codes into its logs.
@@ -928,22 +927,6 @@ export class CustomerAuthService {
HttpStatus.SERVICE_UNAVAILABLE,
);
}
// An audit row, NOT the message body.
//
// SmsMessage.body is listed and full-text searchable on the admin panel's
// SMS grid (admin-misc.service.ts:136 allows `body`, :135 searches it), so
// persisting the real text would show every live OTP to any operator
// holding the `sms` rid — and the whole point of hashing codeHash is that
// the code exists nowhere in readable form. The code is redacted here.
await this.prisma.smsMessage.create({
data: {
audience: MessageAudience.SINGLE_NUMBER,
recipients: mobile,
body: 'کد یکبار مصرف ورود (متن پیام ثبت نمی‌شود)',
sentCount: 1,
},
});
}
}
@@ -334,7 +334,7 @@ describe('OrdersService.createOrder — order SMS', () => {
await service.createOrder(dto, 'c1', { placedBy: 'customer' });
await settle();
const toCustomer = sent.find((s) => s.to === '09120000001');
expect(toCustomer?.body).toContain('به ثبت رسید. تهران تاج');
expect(toCustomer?.body).toContain('به ثبت رسید. هدناگلد');
expect(toCustomer?.body).not.toContain('معلق');
});
+3 -3
View File
@@ -310,18 +310,18 @@ describe('SmsService.send', () => {
/**
* The OTP text keeps the reference's shape (ApiMobileController.php:47) with
* the brand تهران تاج — the first line and the trailing token are the Android SMS
* the brand هدناگلد — the first line and the trailing token are the Android SMS
* Retriever hash, and autofill breaks if either changes.
*/
describe('OTP message template', () => {
const build = (code: string) =>
`<#> code is ${code}\n` +
`رمز عبور یکبار مصرف شما جهت ورود به اپلیکیشن تهران تاج ${code} می باشد\n` +
`رمز عبور یکبار مصرف شما جهت ورود به اپلیکیشن هدناگلد ${code} می باشد\n` +
`aMo6iFyUQaY`;
it('matches the expected text byte for byte', () => {
expect(build('1234')).toBe(
'<#> code is 1234\nرمز عبور یکبار مصرف شما جهت ورود به اپلیکیشن تهران تاج 1234 می باشد\naMo6iFyUQaY',
'<#> code is 1234\nرمز عبور یکبار مصرف شما جهت ورود به اپلیکیشن هدناگلد 1234 می باشد\naMo6iFyUQaY',
);
});
+3 -5
View File
@@ -14,12 +14,10 @@ import { isPrivateIpv4, resolveRelayHost } from './drivers/relay.driver';
*
* Reference behaviour kept for every driver:
* * the message is trim()ed before sending (`Sms.php:77`)
* * EVERY message is persisted, via `Sms::set()` (`:80`)
* * EVERY message is persisted, via `Sms::set()` (`:80`) — including OTP
* login texts (owner request: show codes in the admin SMS list)
* * the response body is returned and the callers ignore it
*
* The OTP message body is NOT persisted in readable form — see
* CustomerAuthService.deliver() and SEC-11.
*
* Failure handling matches the reference: `Sms::Send` is called inline and its
* result is discarded, so a failed send never aborted the order, the document
* approval or the customer creation that triggered it. `send()` therefore
@@ -31,7 +29,7 @@ export type { SmsSendResult } from './drivers';
export interface SmsSendOptions {
/**
* Persist the message body. Default true, matching `Sms::set()`.
* OTP delivery passes false and writes its own redacted row (SEC-11).
* Pass false only when the caller writes its own audit row.
*/
persist?: boolean;
audience?: MessageAudience;