This commit is contained in:
David Arranz 2024-07-12 18:13:53 +02:00
parent 8ad1c63acc
commit 533b22dd98
25 changed files with 424 additions and 115 deletions

View File

@ -10,7 +10,7 @@ import {
} from "lucide-react";
import { Layout, LayoutContent, LayoutHeader } from "@/components";
import { useGetIdentity } from "@/lib/hooks";
import { useGetProfile } from "@/lib/hooks";
import {
Badge,
Button,
@ -49,7 +49,7 @@ import { useNavigate } from "react-router-dom";
export const DashboardPage = () => {
const navigate = useNavigate();
const { data: userIdentity, status } = useGetIdentity();
const { data: userIdentity, status } = useGetProfile();
return (
<Layout>

View File

@ -202,14 +202,8 @@ export const QuoteEdit = () => {
</div>
</div>
<FormCurrencyField
currency={quoteCurrency}
precision={4}
className='text-right'
label={"subtotal_price"}
disabled={form.formState.disabled}
{...form.register("subtotal_price")}
/>
<QuoteGeneralCardEditor />
<QuoteDetailsCardEditor currency={quoteCurrency} />
<Tabs defaultValue='items' className='space-y-4'>
<TabsList>

View File

@ -1,4 +1,4 @@
import { useCustomDialog, useGetIdentity } from "@/lib/hooks";
import { useCustomDialog, useGetProfile } from "@/lib/hooks";
import {
Button,
DropdownMenu,
@ -32,7 +32,7 @@ export const UserButton = () => {
navigate("/logout");
},
});
const { data, status } = useGetIdentity();
const { data, status } = useGetProfile();
const openUserMenu = (event: SyntheticEvent) => {
event.preventDefault();

View File

@ -24,7 +24,7 @@ export const createAxiosAuthActions = (
return {
success: true,
data,
redirectTo: "/",
redirectTo: "/home",
};
} catch (error) {
return {
@ -61,10 +61,18 @@ export const createAxiosAuthActions = (
);
},
getIdentity: async () => {
getProfile: async () => {
/**
* id: string;
* name: string;
* email: string;
* lang_code: string;
* roles: string[];
*/
try {
const result = await httpClient.request<IIdentity_Response_DTO>({
url: `${apiUrl}/auth/identity`,
url: `${apiUrl}/profile`,
method: "GET",
});

View File

@ -1,4 +1,4 @@
import { IIdentity_Response_DTO } from "@shared/contexts";
import { IGetProfileResponse_DTO } from "@shared/contexts";
export type SuccessNotificationResponse = {
message: string;
@ -7,7 +7,7 @@ export type SuccessNotificationResponse = {
export type PermissionResponse = unknown;
export type IdentityResponse = IIdentity_Response_DTO | null;
export type ProfileResponse = IGetProfileResponse_DTO | null;
export type AuthActionCheckResponse = {
authenticated: boolean;
@ -39,5 +39,5 @@ export interface IAuthActions {
forgotPassword?: (params: unknown) => Promise<AuthActionResponse>;
updatePassword?: (params: unknown) => Promise<AuthActionResponse>;
getPermissions?: (params?: Record<string, unknown>) => Promise<PermissionResponse>;
getIdentity?: (params?: unknown) => Promise<IdentityResponse>;
getProfile?: (params?: unknown) => Promise<ProfileResponse>;
}

View File

@ -1,6 +1,6 @@
export * from "./AuthActions";
export * from "./AuthContext";
export * from "./useAuth";
export * from "./useGetIdentity";
export * from "./useGetProfile";
export * from "./useIsLoggedIn";
export * from "./useLogin";

View File

@ -1,16 +0,0 @@
import { IdentityResponse, useAuth } from "@/lib/hooks";
import { UseQueryOptions, useQuery } from "@tanstack/react-query";
import { useQueryKey } from "../useQueryKey";
export const useGetIdentity = (queryOptions?: UseQueryOptions<IdentityResponse>) => {
const keys = useQueryKey();
const { getIdentity } = useAuth();
const result = useQuery<IdentityResponse>({
queryKey: keys().auth().action("identity").get(),
queryFn: getIdentity,
...queryOptions,
});
return result;
};

View File

@ -0,0 +1,16 @@
import { ProfileResponse, useAuth } from "@/lib/hooks";
import { UseQueryOptions, useQuery } from "@tanstack/react-query";
import { useQueryKey } from "../useQueryKey";
export const useGetProfile = (queryOptions?: UseQueryOptions<ProfileResponse>) => {
const keys = useQueryKey();
const { getProfile } = useAuth();
const result = useQuery<ProfileResponse>({
queryKey: keys().auth().action("profile").get(),
queryFn: getProfile,
...queryOptions,
});
return result;
};

View File

@ -17,7 +17,7 @@ export const useLogin = (params?: UseMutationOptions<AuthActionResponse, Error,
onSuccess: (data, variables, context) => {
const { success, redirectTo } = data;
if (success && redirectTo) {
navigate(redirectTo || "/", { replace: true });
navigate(redirectTo || "/home", { replace: false });
}
if (onSuccess) {
onSuccess(data, variables, context);

View File

@ -1,7 +1,7 @@
import { useGetIdentity } from "../useAuth";
import { useGetProfile } from "../useAuth";
export const usePreferredLanguage = () => {
const { data } = useGetIdentity();
const { data } = useGetProfile();
if (!data || !data.language) {
return navigator.languages && navigator.languages.length

View File

@ -16,7 +16,7 @@ type DataMutationActions =
type AuthActionType =
| "login"
| "logout"
| "identity"
| "profile"
| "register"
| "forgotPassword"
| "check"

View File

@ -4,36 +4,243 @@
"cancel": "Cancel",
"no": "No",
"yes": "Yes",
"Accept": "Accept"
"save": "Save",
"accept": "OK",
"hide": "Ocultar",
"back": "Back",
"upload": "Upload",
"continue": "Continue",
"sort_asc": "Asc",
"sort_asc_description": "En order ascendente. Click para ordenar descendentemente.",
"sort_desc": "Desc",
"sort_desc_description": "En orden descendente. Click para ordenar ascendentemente.",
"sort_none_description": "Sin orden. Click para ordenar ascendentemente.",
"rows_selected": "{{count}} de {{total}} fila(s) seleccionadas.",
"rows_per_page": "Filas por página",
"num_page_of_total": "Página {{count}} de {{total}}",
"go_to_first_page": "Ir a la primera página",
"go_to_prev_page": "Ir a la página anterior",
"go_to_next_page": "Ir a la página siguiente",
"go_to_last_page": "Ir a la última página",
"filter_placeholder": "Escribe aquí para filtrar...",
"reset_filter": "Quitar el filtro",
"error": "Error",
"actions": "Acciones",
"open_menu": "Abrir el menú",
"duplicate_rows": "Duplicar",
"duplicate_rows_tooltip": "Duplica las fila(s) seleccionadas(s)",
"pick_date": "Elige una fecha",
"required_field": "Este campo es obligatorio",
"unsaved_changes_prompt": "Los últimos cambios no se han guardado. Si continúas, se perderán",
"edit": "Editar"
},
"main_menu": {
"home": "Home",
"settings": "Settings",
"dealers": "Dealers",
"catalog": "Catalog",
"quotes": "Quotes",
"search_placeholder": "Search product, quotes, etc...",
"home": "Inicio",
"settings": "Ajustes",
"dealers": "Distribuidores",
"catalog": "Catálogo",
"quotes": "Cotizaciones",
"search_placeholder": "Buscar productos, cotizaciones, etc...",
"user": {
"user_menu": "User menu",
"my_account": "My account",
"profile": "Profile",
"settings": "Settings",
"support": "Support",
"logout": "Logout"
"user_menu": "Menú del usuario",
"my_account": "Mi cuenta",
"profile": "Perfil",
"settings": "Ajustes",
"support": "Soporte",
"logout": "Salir"
},
"logout": {}
},
"login_page": {
"title": "Uecko Quotes",
"description": "Enter your email address and password to login",
"title": "Presupuestador para distribuidores",
"description": "Introduzca su dirección de correo electrónico y contraseña para acceder",
"email_label": "Email",
"email_placeholder": "user@sample.com",
"password_label": "Password",
"forgotten_password": "Forgot your password?",
"become_dealer": "Do you want to become a Uecko dealer?",
"contact_us": "Contact us",
"login": "Login",
"error": "Error"
"email_placeholder": "micorreo@ejemplo.com",
"password_label": "Contraseña",
"forgotten_password": "¿Has olvidado tu contraseña?",
"become_dealer": "¿Quieres ser distribuidor de Uecko?",
"contact_us": "Contacta con nosotros",
"login": "Entrar"
},
"dashboard": {
"welcome": "Bienvenido"
},
"catalog": {
"list": {
"title": "Catálogo de artículos",
"columns": {
"description": "Descripción",
"points": "Puntos",
"retail_price": "PVP"
}
}
},
"quotes": {
"list": {
"title": "Quotes",
"subtitle": "",
"tabs": {
"all": "All",
"draft": "Draft",
"archived": "Archive"
},
"columns": {
"date": "Date",
"reference": "Reference",
"status": "Status",
"customer_information": "Customer",
"total_price": "Imp. total"
}
},
"create": {
"title": "New quote",
"buttons": {
"save_quote": "Guardar cotización",
"discard": "Descartar"
},
"tabs": {
"general": "Datos generales",
"items": "Contenido",
"documents": "Documentos",
"history": "Historial"
},
"form_groups": {
"general": {
"title": "Datos generales",
"desc": "Datos generales y cliente al que va la cotización"
},
"status": {
"title": "Estado",
"desc": "Estado de la cotización"
},
"items": {
"title": "Contenido de la cotización",
"desc": "Líneas de detalle de la cotización. Ayúdese del catálogo para rellenar más fácilmente el contenido."
},
"documents": {
"title": "Documentos",
"desc": "Añada adjuntar con su cotización documentos como fotos, planos, croquis, etc."
},
"history": {
"title": "",
"desc": ""
}
},
"edit": {
"title": "Cotización"
}
},
"edit": {
"title": "Cotización"
},
"status": {
"draft": "Borrador"
},
"form_fields": {
"date": {
"label": "Fecha",
"desc": "Fecha de esta cotización",
"placeholder": ""
},
"reference": {
"label": "Referencia",
"desc": "Referencia para esta cotización",
"placeholder": ""
},
"lang_code": {
"label": "Idioma",
"desc": "Idioma de la cotización",
"placeholder": ""
},
"currency_code": {
"label": "Moneda",
"desc": "Moneda de la cotización",
"placeholder": ""
},
"customer_information": {
"label": "Datos del cliente",
"desc": "Escriba el nombre del cliente en la primera línea, la direccion en la segunda y el código postal y ciudad en la tercera.",
"placeholder": "Nombre y apellidos\nCalle y número\nCódigo postal y ciudad..."
},
"payment_method": {
"label": "Forma de pago",
"placeholder": "placeholder",
"desc": "desc"
},
"notes": {
"label": "Notas",
"placeholder": "",
"desc": "desc"
},
"validity": {
"label": "Validez de la cotización",
"placeholder": "",
"desc": "desc"
},
"items": {
"quantity": {
"label": "Cantidad",
"placeholder": "",
"desc": ""
},
"description": {
"label": "Descripción",
"placeholder": "",
"desc": ""
},
"unit_price": {
"label": "Imp. unitario",
"placeholder": "",
"desc": "Importe unitario del artículo"
},
"subtotal_price": {
"label": "Subtotal",
"placeholder": "",
"desc": ""
},
"discount": {
"label": "Dto (%)",
"placeholder": "",
"desc": "Porcentaje de descuento"
},
"total_price": {
"label": "Imp. total",
"placeholder": "",
"desc": "Importe total con el descuento ya aplicado"
}
}
}
},
"settings": {
"title": "Ajustes",
"quotes": {
"title": "Cotizaciones",
"contact_information": {
"label": "Información de contacto",
"placeholder": "placeholder",
"desc": "Información de contacto"
},
"default_payment_method": {
"label": "Forma de pago",
"placeholder": "placeholder",
"desc": "desc"
},
"default_notes": {
"label": "Notas",
"placeholder": "",
"desc": "desc"
},
"default_legal_terms": {
"label": "Cláusulas legales",
"placeholder": "",
"desc": "desc"
},
"default_quote_validity": {
"label": "Validez por defecto",
"placeholder": "",
"desc": ""
}
}
}
}
}

View File

@ -27,6 +27,7 @@ export interface IAuthUser {
isUser: boolean;
isAdmin: boolean;
getRoles: () => AuthUserRole[];
getRolesArray: () => string[];
verifyPassword: (candidatePassword: string) => boolean;
}
@ -77,6 +78,10 @@ export class AuthUser extends AggregateRoot<IAuthUserProps> implements IAuthUser
return this.roles;
}
public getRolesArray(): string[] {
return this.getRoles()?.map((rol) => rol.toString());
}
public verifyPassword(candidatePassword: string): boolean {
return this.props.password.verifyPassword(candidatePassword);
}

View File

@ -15,8 +15,8 @@ export const identityPresenter: IIdentityPresenter = {
id: user.id.toString(),
name: user.name.toString(),
email: user.email.toString(),
language: user.language.toString(),
roles: user.getRoles()?.map((rol) => rol.toString()),
lang_code: user.language.toString(),
roles: user.getRolesArray(),
};
},
};

View File

@ -1,20 +1,40 @@
import { AggregateRoot, IDomainError, Result, UniqueID } from "@shared/contexts";
import { DealerStatus } from "@/contexts/sales/domain";
import {
AggregateRoot,
CurrencyData,
IDomainError,
Language,
Name,
Note,
Result,
UniqueID,
} from "@shared/contexts";
export interface IProfileProps {
contactInformation: string;
defaultPaymentMethod: string;
defaultNotes: string;
defaultLegalTerms: string;
defaultQuoteValidity: string;
name: Name;
language: Language;
status: DealerStatus;
currency: CurrencyData;
contactInformation: Note;
defaultPaymentMethod: Note;
defaultNotes: Note;
defaultLegalTerms: Note;
defaultQuoteValidity: Note;
}
export interface IProfile {
id: UniqueID;
contactInformation: string;
defaultPaymentMethod: string;
defaultNotes: string;
defaultLegalTerms: string;
defaultQuoteValidity: string;
name: Name;
language: Language;
status: DealerStatus;
currency: CurrencyData;
contactInformation: Note;
defaultPaymentMethod: Note;
defaultNotes: Note;
defaultLegalTerms: Note;
defaultQuoteValidity: Note;
}
export class Profile extends AggregateRoot<IProfileProps> implements IProfile {
@ -23,23 +43,39 @@ export class Profile extends AggregateRoot<IProfileProps> implements IProfile {
return Result.ok<Profile>(profile);
}
get contactInformation(): string {
get name(): Name {
return this.props.name;
}
get language(): Language {
return this.props.language;
}
get status(): DealerStatus {
return this.props.status;
}
get currency(): CurrencyData {
return this.props.currency;
}
get contactInformation(): Note {
return this.props.contactInformation;
}
get defaultPaymentMethod(): string {
get defaultPaymentMethod(): Note {
return this.props.defaultPaymentMethod;
}
get defaultNotes(): string {
get defaultNotes(): Note {
return this.props.defaultNotes;
}
get defaultLegalTerms(): string {
get defaultLegalTerms(): Note {
return this.props.defaultLegalTerms;
}
get defaultQuoteValidity(): string {
get defaultQuoteValidity(): Note {
return this.props.defaultQuoteValidity;
}
}

View File

@ -41,7 +41,7 @@ export class ProfileRepository extends SequelizeRepository<Profile> implements I
}
public async getByUserId(userId: UniqueID): Promise<Profile | null> {
const rawDealer: any = await this._getBy("Profile_Model", "user_id", userId.toPrimitive());
const rawDealer: any = await this._getBy("Dealer_Model", "user_id", userId.toPrimitive());
if (!rawDealer === true) {
return null;

View File

@ -1,8 +1,8 @@
import { IUseCaseError, UseCaseError } from "@/contexts/common/application/useCases";
import { ExpressController } from "@/contexts/common/infrastructure/express";
import { User } from "@/contexts/users/domain/entities/User";
import { IGetProfileResponse_DTO } from "@shared/contexts";
import { AuthUser } from "@/contexts/auth/domain";
import { IServerError } from "@/contexts/common/domain/errors";
import { IInfrastructureError, InfrastructureError } from "@/contexts/common/infrastructure";
import { GetProfileUseCase } from "@/contexts/profile/application/GetProfile.useCase";
@ -30,7 +30,7 @@ export class GetProfileController extends ExpressController {
}
async executeImpl(): Promise<any> {
const user = <User | undefined>this.req.user;
const user = <AuthUser>this.req.user;
if (!user) {
const errorMessage = "Unexpected missing user data";
@ -42,17 +42,17 @@ export class GetProfileController extends ExpressController {
}
try {
const result = await this.useCase.execute({
const profileOrError = await this.useCase.execute({
userId: user.id,
});
if (result.isFailure) {
return this._handleExecuteError(result.error);
if (profileOrError.isFailure) {
return this._handleExecuteError(profileOrError.error);
}
const profile = result.object;
const profile = profileOrError.object;
return this.ok<IGetProfileResponse_DTO>(this.presenter.map(profile, this.context));
return this.ok<IGetProfileResponse_DTO>(this.presenter.map(profile, user, this.context));
} catch (e: unknown) {
return this.fail(e as IServerError);
}

View File

@ -1,20 +1,32 @@
import { AuthUser } from "@/contexts/auth/domain";
import { Profile } from "@/contexts/profile/domain";
import { IProfileContext } from "@/contexts/profile/infrastructure/Profile.context";
import { IGetProfileResponse_DTO } from "@shared/contexts";
export interface IGetProfilePresenter {
map: (profile: Profile, context: IProfileContext) => IGetProfileResponse_DTO;
map: (profile: Profile, user: AuthUser, context: IProfileContext) => IGetProfileResponse_DTO;
}
export const GetProfilePresenter: IGetProfilePresenter = {
map: (profile: Profile, context: IProfileContext): IGetProfileResponse_DTO => {
map: (profile: Profile, user: AuthUser, context: IProfileContext): IGetProfileResponse_DTO => {
return {
dealer_id: profile.id.toString(),
contact_information: profile.contactInformation,
default_payment_method: profile.defaultPaymentMethod,
default_notes: profile.defaultNotes,
default_legal_terms: profile.defaultLegalTerms,
default_quote_validity: profile.defaultQuoteValidity,
name: user.name.toString(),
email: user.email.toString(),
lang_code: user.language.code,
roles: user.getRolesArray(),
dealer: {
id: profile.id.toString(),
name: profile.name.toString(),
status: profile.status.toString(),
lang_code: profile.language.toString(),
currency_code: profile.currency.toString(),
contact_information: profile.contactInformation.toString(),
default_payment_method: profile.defaultPaymentMethod.toString(),
default_notes: profile.defaultNotes.toString(),
default_legal_terms: profile.defaultLegalTerms.toString(),
default_quote_validity: profile.defaultQuoteValidity.toString(),
},
};
},
};

View File

@ -3,7 +3,9 @@ import {
MapperParamsType,
SequelizeMapper,
} from "@/contexts/common/infrastructure";
import { UniqueID } from "@shared/contexts";
import { DealerStatus } from "@/contexts/sales/domain";
import { Dealer_Model } from "@/contexts/sales/infrastructure/sequelize";
import { CurrencyData, Language, Name, Note, UniqueID } from "@shared/contexts";
import { IProfileProps, Profile } from "../../domain";
import { IProfileContext } from "../Profile.context";
import { ProfileCreationAttributes, Profile_Model } from "../sequelize";
@ -19,13 +21,29 @@ class ProfileMapper
super(props);
}
protected toDomainMappingImpl(source: Profile_Model, params: any): Profile {
protected toDomainMappingImpl(source: Dealer_Model, params: any): Profile {
const name = this.mapsValue(source, "name", Name.create);
const status = this.mapsValue(source, "status", DealerStatus.create);
const language = this.mapsValue(source, "lang_code", Language.createFromCode);
const currency = this.mapsValue(source, "currency_code", CurrencyData.createFromCode);
const contactInformation = this.mapsValue(source, "contact_information", Note.create);
const defaultPaymentMethod = this.mapsValue(source, "default_payment_method", Note.create);
const defaultNotes = this.mapsValue(source, "default_notes", Note.create);
const defaultLegalTerms = this.mapsValue(source, "default_legal_terms", Note.create);
const defaultQuoteValidity = this.mapsValue(source, "default_quote_validity", Note.create);
const props: IProfileProps = {
contactInformation: source.contact_information,
defaultPaymentMethod: source.default_payment_method,
defaultNotes: source.default_notes,
defaultLegalTerms: source.default_legal_terms,
defaultQuoteValidity: source.default_quote_validity,
name,
status,
language,
currency,
contactInformation,
defaultPaymentMethod,
defaultNotes,
defaultLegalTerms,
defaultQuoteValidity,
};
const id = this.mapsValue(source, "id", UniqueID.create);

View File

@ -1,5 +1,6 @@
import {
AggregateRoot,
CurrencyData,
IDomainError,
KeyValueMap,
Language,
@ -12,10 +13,11 @@ import { DealerStatus } from "./DealerStatus";
export interface IDealerProps {
user_id: UniqueID;
name: Name;
logo: string;
//logo: string;
language: Language;
additionalInfo: KeyValueMap;
status: DealerStatus;
currency: CurrencyData;
}
export interface IDealer {
@ -26,6 +28,7 @@ export interface IDealer {
additionalInfo: KeyValueMap;
status: DealerStatus;
currency: CurrencyData;
}
export class Dealer extends AggregateRoot<IDealerProps> implements IDealer {
@ -53,4 +56,8 @@ export class Dealer extends AggregateRoot<IDealerProps> implements IDealer {
get additionalInfo(): KeyValueMap {
return this.props.additionalInfo;
}
get currency(): CurrencyData {
return this.props.currency;
}
}

View File

@ -3,7 +3,7 @@ import {
MapperParamsType,
SequelizeMapper,
} from "@/contexts/common/infrastructure";
import { KeyValueMap, Language, Name, UniqueID } from "@shared/contexts";
import { CurrencyData, KeyValueMap, Language, Name, UniqueID } from "@shared/contexts";
import { Dealer, DealerStatus, IDealerProps } from "../../domain/entities";
import { ISalesContext } from "../Sales.context";
import { DealerCreationAttributes, Dealer_Model } from "../sequelize";
@ -24,6 +24,7 @@ class DealerMapper
const user_id = this.mapsValue(source, "user_id", UniqueID.create);
const status = this.mapsValue(source, "status", DealerStatus.create);
const language = this.mapsValue(source, "lang_code", Language.createFromCode);
const currency = this.mapsValue(source, "currency_code", CurrencyData.createFromCode);
const additionalInfoOrError = KeyValueMap.create([
["contact_information", source.contact_information],
@ -35,10 +36,11 @@ class DealerMapper
const props: IDealerProps = {
user_id,
logo: "",
//logo: "",
name,
status,
language,
currency,
additionalInfo: additionalInfoOrError.object,
};
@ -57,7 +59,8 @@ class DealerMapper
id: source.id.toPrimitive(),
user_id: source.user_id.toPrimitive(),
//contact_id: undefined,
logo: "",
currency_code: source.currency.toPrimitive(),
//logo: "",
name: source.name.toPrimitive(),
status: source.status.toPrimitive(),
lang_code: source.language.toPrimitive(),

View File

@ -55,6 +55,7 @@ export class Dealer_Model extends Model<
declare default_quote_validity: CreationOptional<string>;
declare status: CreationOptional<string>;
declare lang_code: CreationOptional<string>;
declare currency_code: CreationOptional<string>;
declare user: NonAttribute<User_Model>;
declare quotes: NonAttribute<Quote_Model>;
@ -90,6 +91,12 @@ export default (sequelize: Sequelize) => {
defaultValue: "es",
},
currency_code: {
type: new DataTypes.STRING(3),
allowNull: false,
defaultValue: "EUR",
},
status: {
type: DataTypes.STRING,
allowNull: false,

View File

@ -1,5 +1,4 @@
import { checkUser, createLoginController } from "@/contexts/auth";
import { createIdentityController } from "@/contexts/auth/infrastructure/express/controllers/identity";
import Express from "express";
import passport from "passport";
@ -27,12 +26,15 @@ export const authRouter = (appRouter: Express.Router) => {
authRoutes.post("/register");
/*
-> Sustituido por /profile
authRoutes.get(
"/identity",
checkUser,
(req: Express.Request, res: Express.Response, next: Express.NextFunction) =>
createIdentityController(res.locals["context"]).execute(req, res, next)
);
);*/
appRouter.use("/auth", authRoutes);
};

View File

@ -2,6 +2,6 @@ export interface IIdentity_Response_DTO {
id: string;
name: string;
email: string;
language: string;
lang_code: string;
roles: string[];
}

View File

@ -1,8 +1,18 @@
export interface IGetProfileResponse_DTO {
dealer_id: string;
contact_information: string;
default_payment_method: string;
default_notes: string;
default_legal_terms: string;
default_quote_validity: string;
name: string;
email: string;
lang_code: string;
roles: string[];
dealer: {
id: string;
name: string;
contact_information: string;
default_payment_method: string;
default_notes: string;
default_legal_terms: string;
default_quote_validity: string;
status: string;
lang_code: string;
currency_code: string;
};
}