diff --git a/src/app/domains/consumer/models/io.d.ts b/src/app/domains/consumer/models/io.d.ts index bd4bc85..d6267b2 100644 --- a/src/app/domains/consumer/models/io.d.ts +++ b/src/app/domains/consumer/models/io.d.ts @@ -1,9 +1,31 @@ +import { IEnumTranslate } from '@/shared/models/enum_translate.type'; + export interface IConsumerInfoRawResponse { id: string; + partner: Partner; + type: IEnumTranslate<'INDIVIDUAL' | 'LEGAL'>; + status: IEnumTranslate<'ACTIVE' | 'INACTIVE'>; + legal?: Legal; + individual?: Individual; + name: string; +} +export interface IConsumerInfoResponse extends IConsumerInfoRawResponse {} + +interface Individual { first_name: string; last_name: string; mobile_number: string; - status: string; + national_code: string; fullname: string; } -export interface IConsumerInfoResponse extends IConsumerInfoRawResponse {} + +interface Legal { + company_name: string; + registration_number: string; +} + +interface Partner { + id: string; + name: string; + code: string; +} diff --git a/src/app/domains/consumer/modules/profile/components/form.component.html b/src/app/domains/consumer/modules/profile/components/form.component.html new file mode 100644 index 0000000..8f0dd94 --- /dev/null +++ b/src/app/domains/consumer/modules/profile/components/form.component.html @@ -0,0 +1,21 @@ + +
+ @if (profileType === "LEGAL") { + + + } @else { + + + + + } + + +
diff --git a/src/app/domains/consumer/modules/profile/components/form.component.ts b/src/app/domains/consumer/modules/profile/components/form.component.ts new file mode 100644 index 0000000..0721ba2 --- /dev/null +++ b/src/app/domains/consumer/modules/profile/components/form.component.ts @@ -0,0 +1,76 @@ +import { AbstractFormDialog } from '@/shared/abstractClasses'; +import { + CompanyNameComponent, + FirstNameComponent, + LastNameComponent, + MobileNumberComponent, + NationalCodeComponent, + RegistrationNumberComponent, + SharedDialogComponent, +} from '@/shared/components'; +import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; +import { fieldControl } from '@/shared/constants'; +import { Component, inject, Input } from '@angular/core'; +import { ReactiveFormsModule } from '@angular/forms'; +import { IProfileRequestPayload, IProfileResponse } from '../models'; +import { ProfileService } from '../services/main.service'; + +@Component({ + selector: 'consumer-profile-form', + templateUrl: 'form.component.html', + imports: [ + ReactiveFormsModule, + FormFooterActionsComponent, + FirstNameComponent, + LastNameComponent, + NationalCodeComponent, + MobileNumberComponent, + CompanyNameComponent, + RegistrationNumberComponent, + SharedDialogComponent, + ], +}) +export class ConsumerProfileFormComponent extends AbstractFormDialog< + IProfileRequestPayload, + IProfileResponse, + IProfileRequestPayload +> { + @Input({ required: true }) profileType!: 'INDIVIDUAL' | 'LEGAL'; + + private readonly service = inject(ProfileService); + + initForm = () => { + const form = this.fb.group({ + first_name: fieldControl.first_name(this.initialValues?.first_name), + last_name: fieldControl.last_name(this.initialValues?.last_name), + mobile_number: fieldControl.mobile_number(this.initialValues?.mobile_number), + national_code: fieldControl.national_code(this.initialValues?.national_code), + company_name: fieldControl.company_name(this.initialValues?.company_name), + registration_number: fieldControl.registration_number( + this.initialValues?.registration_number, + ), + }); + + if (this.profileType === 'LEGAL') { + // @ts-ignore + form.removeControl('first_name'); + // @ts-ignore + form.removeControl('last_name'); + // @ts-ignore + form.removeControl('mobile_number'); + // @ts-ignore + form.removeControl('national_code'); + } else { + // @ts-ignore + form.removeControl('company_name'); + // @ts-ignore + form.removeControl('registration_number'); + } + return form; + }; + override form = this.initForm(); + + override submitForm(payload: IProfileRequestPayload) { + return this.service.updateProfile(payload); + } +} diff --git a/src/app/domains/consumer/modules/profile/components/reset-password-card.component.html b/src/app/domains/consumer/modules/profile/components/reset-password-card.component.html new file mode 100644 index 0000000..70ce531 --- /dev/null +++ b/src/app/domains/consumer/modules/profile/components/reset-password-card.component.html @@ -0,0 +1,11 @@ + +
+ + + +
diff --git a/src/app/domains/consumer/modules/profile/components/reset-password-card.component.ts b/src/app/domains/consumer/modules/profile/components/reset-password-card.component.ts new file mode 100644 index 0000000..114f35b --- /dev/null +++ b/src/app/domains/consumer/modules/profile/components/reset-password-card.component.ts @@ -0,0 +1,46 @@ +import { ToastService } from '@/core/services/toast.service'; +import { MustMatch } from '@/core/validators'; +import { AppCardComponent, SharedPasswordInputComponent } from '@/shared/components'; +import { fieldControl } from '@/shared/constants'; +import { Component, inject, signal } from '@angular/core'; +import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; +import { ButtonDirective } from 'primeng/button'; +import { finalize } from 'rxjs'; +import { ProfileService } from '../services/main.service'; + +@Component({ + selector: 'consumer-reset-password-card', + templateUrl: './reset-password-card.component.html', + imports: [ReactiveFormsModule, AppCardComponent, SharedPasswordInputComponent, ButtonDirective], +}) +export class ConsumerResetPasswordCardComponent { + private readonly service = inject(ProfileService); + private readonly fb = inject(FormBuilder); + private readonly toastService = inject(ToastService); + + readonly loading = signal(false); + + form = this.fb.group( + { + password: fieldControl.password(), + confirmPassword: fieldControl.confirmPassword(), + }, + { validators: [MustMatch('password', 'confirmPassword')] }, + ); + + submit() { + if (this.form.invalid) { + this.form.markAllAsTouched(); + return; + } + + this.loading.set(true); + this.service + .resetPassword({ password: this.form.value.password as string }) + .pipe(finalize(() => this.loading.set(false))) + .subscribe(() => { + this.form.reset(); + this.toastService.success({ text: 'گذرواژه با موفقیت به‌روز شد.' }); + }); + } +} diff --git a/src/app/domains/consumer/modules/profile/constants/apiRoutes/index.ts b/src/app/domains/consumer/modules/profile/constants/apiRoutes/index.ts new file mode 100644 index 0000000..95e8587 --- /dev/null +++ b/src/app/domains/consumer/modules/profile/constants/apiRoutes/index.ts @@ -0,0 +1,7 @@ +const baseUrl = '/api/v1/consumer'; + +export const CONSUMER_PROFILE_API_ROUTES = { + info: () => `${baseUrl}`, + updateInfo: () => `${baseUrl}`, + resetPassword: () => `${baseUrl}/update-password`, +}; diff --git a/src/app/domains/consumer/modules/profile/constants/index.ts b/src/app/domains/consumer/modules/profile/constants/index.ts new file mode 100644 index 0000000..ee61bd7 --- /dev/null +++ b/src/app/domains/consumer/modules/profile/constants/index.ts @@ -0,0 +1,2 @@ +export * from './apiRoutes'; +export * from './routes'; diff --git a/src/app/domains/consumer/modules/profile/constants/routes/index.ts b/src/app/domains/consumer/modules/profile/constants/routes/index.ts new file mode 100644 index 0000000..0d82fb2 --- /dev/null +++ b/src/app/domains/consumer/modules/profile/constants/routes/index.ts @@ -0,0 +1,17 @@ +import { NamedRoutes } from '@/core'; +import { Routes } from '@angular/router'; + +export type TConsumerProfileRouteNames = 'profile'; + +export const consumerProfileNamedRoutes: NamedRoutes = { + profile: { + path: 'profile', + loadComponent: () => import('../../views/single.component').then((m) => m.ConsumerProfileComponent), + meta: { + title: 'پروفایل', + pagePath: () => '/consumer/profile', + }, + }, +}; + +export const CONSUMER_PROFILE_ROUTES: Routes = [consumerProfileNamedRoutes.profile]; diff --git a/src/app/domains/consumer/modules/profile/models/index.ts b/src/app/domains/consumer/modules/profile/models/index.ts new file mode 100644 index 0000000..d7b593e --- /dev/null +++ b/src/app/domains/consumer/modules/profile/models/index.ts @@ -0,0 +1 @@ +export * from './io.d'; diff --git a/src/app/domains/consumer/modules/profile/models/io.d.ts b/src/app/domains/consumer/modules/profile/models/io.d.ts new file mode 100644 index 0000000..23061ca --- /dev/null +++ b/src/app/domains/consumer/modules/profile/models/io.d.ts @@ -0,0 +1,16 @@ +import { IConsumerInfoResponse } from '@/domains/consumer/models'; + +export interface IProfileResponse extends IConsumerInfoResponse {} + +export interface IProfileRequestPayload { + first_name?: string; + last_name?: string; + mobile_number?: string; + national_code?: string; + company_name?: string; + registration_number?: string; +} + +export interface IResetPasswordRequest { + password: string; +} diff --git a/src/app/domains/consumer/modules/profile/services/main.service.ts b/src/app/domains/consumer/modules/profile/services/main.service.ts new file mode 100644 index 0000000..7ce19de --- /dev/null +++ b/src/app/domains/consumer/modules/profile/services/main.service.ts @@ -0,0 +1,22 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { CONSUMER_PROFILE_API_ROUTES } from '../constants'; +import { IProfileRequestPayload, IProfileResponse, IResetPasswordRequest } from '../models'; + +@Injectable({ providedIn: 'root' }) +export class ProfileService { + constructor(private readonly http: HttpClient) {} + + getInfo(): Observable { + return this.http.get(CONSUMER_PROFILE_API_ROUTES.info()); + } + + updateProfile(payload: IProfileRequestPayload): Observable { + return this.http.patch(CONSUMER_PROFILE_API_ROUTES.updateInfo(), payload); + } + + resetPassword(payload: IResetPasswordRequest): Observable { + return this.http.put(CONSUMER_PROFILE_API_ROUTES.resetPassword(), payload); + } +} diff --git a/src/app/domains/consumer/modules/profile/views/single.component.html b/src/app/domains/consumer/modules/profile/views/single.component.html new file mode 100644 index 0000000..ed3c129 --- /dev/null +++ b/src/app/domains/consumer/modules/profile/views/single.component.html @@ -0,0 +1,28 @@ +
+ + @if (isLegal()) { +
+ + +
+ } @else { +
+ + + + +
+ } +
+ + + + @if (editing()) { + + } +
diff --git a/src/app/domains/consumer/modules/profile/views/single.component.ts b/src/app/domains/consumer/modules/profile/views/single.component.ts new file mode 100644 index 0000000..8520b4a --- /dev/null +++ b/src/app/domains/consumer/modules/profile/views/single.component.ts @@ -0,0 +1,32 @@ +import { ConsumerStore } from '@/domains/consumer/store/main.store'; +import { AppCardComponent, KeyValueComponent } from '@/shared/components'; +import { Component, computed, inject, signal } from '@angular/core'; +import { ConsumerProfileFormComponent } from '../components/form.component'; +import { ConsumerResetPasswordCardComponent } from '../components/reset-password-card.component'; + +@Component({ + selector: 'consumer-profile', + templateUrl: './single.component.html', + imports: [ + AppCardComponent, + KeyValueComponent, + ConsumerResetPasswordCardComponent, + ConsumerProfileFormComponent, + ], +}) +export class ConsumerProfileComponent { + private readonly store = inject(ConsumerStore); + + readonly profile = computed(() => this.store.entity()); + readonly isLegal = computed(() => this.profile()?.type?.value === 'LEGAL'); + + editing = signal(false); + + edit() { + this.editing.set(true); + } + + onEditSuccess() { + this.store.getData(); + } +} diff --git a/src/app/domains/consumer/routes.ts b/src/app/domains/consumer/routes.ts index 8fd52a3..f6fc5ec 100644 --- a/src/app/domains/consumer/routes.ts +++ b/src/app/domains/consumer/routes.ts @@ -3,6 +3,7 @@ import { CONSUMER_ACCOUNTS_ROUTES } from './modules/accounts/constants'; import { CONSUMER_BUSINESS_ACTIVITIES_ROUTES } from './modules/businessActivities/constants'; import { CONSUMER_CUSTOMERS_ROUTES } from './modules/customers/constants'; import { CONSUMER_POSES_ROUTES } from './modules/poses/constants'; +import { CONSUMER_PROFILE_ROUTES } from './modules/profile/constants'; import { CONSUMER_SALE_INVOICES_ROUTES } from './modules/saleInvoices/constants'; export const CONSUMER_ROUTES = { @@ -19,5 +20,6 @@ export const CONSUMER_ROUTES = { ...CONSUMER_CUSTOMERS_ROUTES, ...CONSUMER_SALE_INVOICES_ROUTES, ...CONSUMER_POSES_ROUTES, + ...CONSUMER_PROFILE_ROUTES, ], } as Route; diff --git a/src/app/shared/abstractClasses/abstract-form-dialog.component.ts b/src/app/shared/abstractClasses/abstract-form-dialog.component.ts index b685f8c..cf094d8 100644 --- a/src/app/shared/abstractClasses/abstract-form-dialog.component.ts +++ b/src/app/shared/abstractClasses/abstract-form-dialog.component.ts @@ -7,10 +7,11 @@ import { AbstractForm } from './abstract-form'; template: '', imports: [ReactiveFormsModule], }) -export abstract class AbstractFormDialog extends AbstractForm< +export abstract class AbstractFormDialog< Request, - Response -> { + Response, + initialValue = Response, +> extends AbstractForm { @Input() set visible(v: boolean) { this.visibleSignal.set(!!v);