refactor: streamline state initialization across stores using defaultBaseStateData
- Replaced individual state properties (loading, error, entity, initialized, isRefreshing) with a spread of defaultBaseStateData in various entity stores including AccountStore, BusinessActivityStore, ConsumerComplexStore, PosStore, and others. - Updated imports to include defaultBaseStateData in relevant store files. - Enhanced consistency in state management across multiple stores. feat: add partner profile management components and services - Introduced PartnerProfileFormComponent and PartnerResetPasswordCardComponent for profile editing and password reset functionalities. - Created ProfileService to handle API interactions for partner profile management. - Added routes and constants for partner profile API and navigation. - Implemented UI components for displaying and editing partner profile information in single.component.html and single.component.ts.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { EntityState, EntityStore } from '@/core/state';
|
||||
import { defaultBaseStateData, EntityState, EntityStore } from '@/core/state';
|
||||
import { computed, inject, Injectable } from '@angular/core';
|
||||
import { MenuItem } from 'primeng/api';
|
||||
import { catchError, finalize } from 'rxjs';
|
||||
@@ -20,11 +20,7 @@ export class BusinessActivityStore extends EntityStore<
|
||||
private readonly service = inject(PartnerConsumerBusinessActivitiesService);
|
||||
constructor() {
|
||||
super({
|
||||
loading: false,
|
||||
error: null,
|
||||
entity: null,
|
||||
initialized: false,
|
||||
isRefreshing: false,
|
||||
...defaultBaseStateData,
|
||||
breadcrumbItems: [],
|
||||
});
|
||||
}
|
||||
@@ -43,8 +39,10 @@ export class BusinessActivityStore extends EntityStore<
|
||||
},
|
||||
{
|
||||
title: this.entity()?.name,
|
||||
routerLink: partnerConsumerBusinessActivitiesNamedRoutes.businessActivity.meta
|
||||
.pagePath!(consumerId, businessId),
|
||||
routerLink: partnerConsumerBusinessActivitiesNamedRoutes.businessActivity.meta.pagePath!(
|
||||
consumerId,
|
||||
businessId,
|
||||
),
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -71,11 +69,7 @@ export class BusinessActivityStore extends EntityStore<
|
||||
|
||||
override reset(): void {
|
||||
this.setState({
|
||||
loading: false,
|
||||
error: null,
|
||||
entity: null,
|
||||
initialized: false,
|
||||
isRefreshing: false,
|
||||
...defaultBaseStateData,
|
||||
breadcrumbItems: [],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { EntityState, EntityStore } from '@/core/state';
|
||||
import { defaultBaseStateData, EntityState, EntityStore } from '@/core/state';
|
||||
import { computed, inject, Injectable } from '@angular/core';
|
||||
import { MenuItem } from 'primeng/api';
|
||||
import { catchError, finalize } from 'rxjs';
|
||||
@@ -17,11 +17,7 @@ export class ComplexStore extends EntityStore<IComplexResponse, ComplexState> {
|
||||
private readonly service = inject(PartnerComplexesService);
|
||||
constructor() {
|
||||
super({
|
||||
loading: false,
|
||||
error: null,
|
||||
entity: null,
|
||||
initialized: false,
|
||||
isRefreshing: false,
|
||||
...defaultBaseStateData,
|
||||
breadcrumbItems: [],
|
||||
});
|
||||
}
|
||||
@@ -71,11 +67,7 @@ export class ComplexStore extends EntityStore<IComplexResponse, ComplexState> {
|
||||
|
||||
override reset(): void {
|
||||
this.setState({
|
||||
loading: false,
|
||||
error: null,
|
||||
entity: null,
|
||||
initialized: false,
|
||||
isRefreshing: false,
|
||||
...defaultBaseStateData,
|
||||
breadcrumbItems: [],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { EntityState, EntityStore } from '@/core/state';
|
||||
import { defaultBaseStateData, EntityState, EntityStore } from '@/core/state';
|
||||
import { computed, inject, Injectable } from '@angular/core';
|
||||
import { MenuItem } from 'primeng/api';
|
||||
import { catchError, finalize } from 'rxjs';
|
||||
@@ -17,11 +17,7 @@ export class ConsumerStore extends EntityStore<IPartnerConsumerResponse, Consume
|
||||
private readonly service = inject(ConsumersService);
|
||||
constructor() {
|
||||
super({
|
||||
loading: false,
|
||||
error: null,
|
||||
entity: null,
|
||||
initialized: false,
|
||||
isRefreshing: false,
|
||||
...defaultBaseStateData,
|
||||
breadcrumbItems: [],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { EntityState, EntityStore } from '@/core/state';
|
||||
import { defaultBaseStateData, EntityState, EntityStore } from '@/core/state';
|
||||
import { computed, inject, Injectable } from '@angular/core';
|
||||
import { MenuItem } from 'primeng/api';
|
||||
import { catchError, finalize } from 'rxjs';
|
||||
@@ -17,11 +17,7 @@ export class PosStore extends EntityStore<IPosResponse, PosState> {
|
||||
private readonly service = inject(PartnerPosesService);
|
||||
constructor() {
|
||||
super({
|
||||
loading: false,
|
||||
error: null,
|
||||
entity: null,
|
||||
initialized: false,
|
||||
isRefreshing: false,
|
||||
...defaultBaseStateData,
|
||||
breadcrumbItems: [],
|
||||
});
|
||||
}
|
||||
@@ -74,11 +70,7 @@ export class PosStore extends EntityStore<IPosResponse, PosState> {
|
||||
|
||||
override reset(): void {
|
||||
this.setState({
|
||||
loading: false,
|
||||
error: null,
|
||||
entity: null,
|
||||
initialized: false,
|
||||
isRefreshing: false,
|
||||
...defaultBaseStateData,
|
||||
breadcrumbItems: [],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { EntityState, EntityStore } from '@/core/state';
|
||||
import { defaultBaseStateData, EntityState, EntityStore } from '@/core/state';
|
||||
import { computed, inject, Injectable } from '@angular/core';
|
||||
import { MenuItem } from 'primeng/api';
|
||||
import { catchError, finalize } from 'rxjs';
|
||||
@@ -17,11 +17,7 @@ export class PartnerCustomerStore extends EntityStore<ICustomerResponse, Partner
|
||||
private readonly service = inject(CustomersService);
|
||||
constructor() {
|
||||
super({
|
||||
loading: false,
|
||||
error: null,
|
||||
entity: null,
|
||||
initialized: false,
|
||||
isRefreshing: false,
|
||||
...defaultBaseStateData,
|
||||
breadcrumbItems: [],
|
||||
});
|
||||
}
|
||||
@@ -64,11 +60,7 @@ export class PartnerCustomerStore extends EntityStore<ICustomerResponse, Partner
|
||||
|
||||
override reset(): void {
|
||||
this.setState({
|
||||
loading: false,
|
||||
error: null,
|
||||
entity: null,
|
||||
initialized: false,
|
||||
isRefreshing: false,
|
||||
...defaultBaseStateData,
|
||||
breadcrumbItems: [],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<shared-dialog
|
||||
header="ویرایش اطلاعات پروفایل"
|
||||
[(visible)]="visible"
|
||||
[modal]="true"
|
||||
[style]="{ width: '500px' }"
|
||||
[closable]="true"
|
||||
(onHide)="close()"
|
||||
>
|
||||
<form [formGroup]="form" (submit)="submit()">
|
||||
<app-shared-upload-file
|
||||
accept="image/*"
|
||||
name="image"
|
||||
[initial_file_url]="initialValues?.logo_url || ''"
|
||||
(onSelect)="changeFile($event)"
|
||||
/>
|
||||
|
||||
<field-name [control]="form.controls.name" />
|
||||
<field-code [control]="form.controls.code" />
|
||||
<app-form-footer-actions [loading]="submitLoading()" (onCancel)="close()" />
|
||||
</form>
|
||||
</shared-dialog>
|
||||
@@ -0,0 +1,65 @@
|
||||
import { Maybe } from '@/core';
|
||||
import { AbstractFormDialog } from '@/shared/abstractClasses';
|
||||
import { CodeComponent, NameComponent, SharedDialogComponent } from '@/shared/components';
|
||||
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
|
||||
import { onSelectFileArgs } from '@/shared/components/uploadFile/model';
|
||||
import { SharedUploadFileComponent } from '@/shared/components/uploadFile/upload-file.component';
|
||||
import { fieldControl } from '@/shared/constants';
|
||||
import { buildFormData } from '@/utils';
|
||||
import { Component, inject, Input, signal } from '@angular/core';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { IProfileRequestPayload, IProfileResponse } from '../models';
|
||||
import { ProfileService } from '../services/main.service';
|
||||
|
||||
@Component({
|
||||
selector: 'partner-profile-form',
|
||||
templateUrl: 'form.component.html',
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
FormFooterActionsComponent,
|
||||
SharedDialogComponent,
|
||||
NameComponent,
|
||||
CodeComponent,
|
||||
SharedUploadFileComponent,
|
||||
],
|
||||
})
|
||||
export class PartnerProfileFormComponent extends AbstractFormDialog<
|
||||
IProfileRequestPayload,
|
||||
IProfileResponse
|
||||
> {
|
||||
@Input() partnerId?: string;
|
||||
private service = inject(ProfileService);
|
||||
|
||||
private initForm() {
|
||||
const formConfig = {
|
||||
name: fieldControl.name(this.initialValues?.name || ''),
|
||||
code: fieldControl.code(this.initialValues?.code || ''),
|
||||
};
|
||||
|
||||
const form = this.fb.group(formConfig);
|
||||
|
||||
return form;
|
||||
}
|
||||
|
||||
logo = signal<Maybe<File>>(null);
|
||||
|
||||
form = this.initForm();
|
||||
|
||||
changeFile(payload: onSelectFileArgs) {
|
||||
this.logo.set(payload.file);
|
||||
}
|
||||
|
||||
override ngOnChanges() {
|
||||
this.form = this.initForm();
|
||||
this.form.patchValue((this.initialValues ?? {}) as Partial<Record<string, unknown>>);
|
||||
if (this.editMode && !this.partnerId) {
|
||||
throw 'missing some arguments';
|
||||
}
|
||||
}
|
||||
|
||||
override submitForm(payload: IProfileRequestPayload) {
|
||||
const formData = buildFormData(this.form, { logo: this.logo() });
|
||||
|
||||
return this.service.updateProfile(formData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<app-card-data cardTitle="تغییر گذرواژه" [editable]="false">
|
||||
<form [formGroup]="form" (submit)="submit()" class="max-w-lg mx-auto">
|
||||
<shared-password-input
|
||||
[passwordControl]="form.controls.password"
|
||||
[confirmPasswordControl]="form.controls.confirmPassword"
|
||||
/>
|
||||
<button type="submit" pButton [disabled]="form.invalid || loading()" class="w-full max-w-xs mx-auto">
|
||||
تغییر گذرواژه
|
||||
</button>
|
||||
</form>
|
||||
</app-card-data>
|
||||
@@ -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: 'partner-reset-password-card',
|
||||
templateUrl: './reset-password-card.component.html',
|
||||
imports: [ReactiveFormsModule, AppCardComponent, SharedPasswordInputComponent, ButtonDirective],
|
||||
})
|
||||
export class PartnerResetPasswordCardComponent {
|
||||
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: 'گذرواژه با موفقیت بهروز شد.' });
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
const baseUrl = '/api/v1/partner';
|
||||
|
||||
export const PARTNER_PROFILE_API_ROUTES = {
|
||||
info: () => `${baseUrl}`,
|
||||
updateInfo: () => `${baseUrl}`,
|
||||
resetPassword: () => `${baseUrl}/update-password`,
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './apiRoutes';
|
||||
export * from './routes';
|
||||
@@ -0,0 +1,18 @@
|
||||
import { NamedRoutes } from '@/core';
|
||||
import { Routes } from '@angular/router';
|
||||
|
||||
export type TPartnerProfileRouteNames = 'profile';
|
||||
|
||||
export const partnerProfileNamedRoutes: NamedRoutes<TPartnerProfileRouteNames> = {
|
||||
profile: {
|
||||
path: 'profile',
|
||||
loadComponent: () =>
|
||||
import('../../views/single.component').then((m) => m.PartnerProfileComponent),
|
||||
meta: {
|
||||
title: 'پروفایل',
|
||||
pagePath: () => '/partner/profile',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const PARTNER_PROFILE_ROUTES: Routes = [partnerProfileNamedRoutes.profile];
|
||||
@@ -0,0 +1 @@
|
||||
export * from './io.d';
|
||||
@@ -0,0 +1,16 @@
|
||||
import { IPartnerRawResponse } from '@/domains/partner/models';
|
||||
|
||||
export interface IProfileResponse extends IPartnerRawResponse {}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { PARTNER_PROFILE_API_ROUTES } from '../constants';
|
||||
import { IProfileResponse, IResetPasswordRequest } from '../models';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ProfileService {
|
||||
constructor(private readonly http: HttpClient) {}
|
||||
|
||||
getInfo(): Observable<IProfileResponse> {
|
||||
return this.http.get<IProfileResponse>(PARTNER_PROFILE_API_ROUTES.info());
|
||||
}
|
||||
|
||||
updateProfile(payload: FormData): Observable<IProfileResponse> {
|
||||
return this.http.patch<IProfileResponse>(PARTNER_PROFILE_API_ROUTES.updateInfo(), payload);
|
||||
}
|
||||
|
||||
resetPassword(payload: IResetPasswordRequest): Observable<unknown> {
|
||||
return this.http.put(PARTNER_PROFILE_API_ROUTES.resetPassword(), payload);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<div class="flex flex-col gap-4">
|
||||
<app-card-data cardTitle="اطلاعات پروفایل" [editable]="true" [(editMode)]="editing">
|
||||
<div class="listKeyValue">
|
||||
<app-key-value label="تصویر" [value]="profile()?.logo_url" type="thumbnail" />
|
||||
<app-key-value label="عنوان" [value]="profile()?.name" />
|
||||
<app-key-value label="کد" [value]="profile()?.code" />
|
||||
</div>
|
||||
</app-card-data>
|
||||
|
||||
<partner-reset-password-card />
|
||||
|
||||
@if (editing()) {
|
||||
<partner-profile-form [(visible)]="editing" [initialValues]="profile()!" (onSubmit)="onEditSuccess()" />
|
||||
}
|
||||
</div>
|
||||
@@ -0,0 +1,31 @@
|
||||
import { PartnerStore } from '@/domains/partner/store/main.store';
|
||||
import { AppCardComponent, KeyValueComponent } from '@/shared/components';
|
||||
import { Component, computed, inject, signal } from '@angular/core';
|
||||
import { PartnerProfileFormComponent } from '../components/form.component';
|
||||
import { PartnerResetPasswordCardComponent } from '../components/reset-password-card.component';
|
||||
|
||||
@Component({
|
||||
selector: 'partner-profile',
|
||||
templateUrl: './single.component.html',
|
||||
imports: [
|
||||
AppCardComponent,
|
||||
KeyValueComponent,
|
||||
PartnerResetPasswordCardComponent,
|
||||
PartnerProfileFormComponent,
|
||||
],
|
||||
})
|
||||
export class PartnerProfileComponent {
|
||||
private readonly store = inject(PartnerStore);
|
||||
|
||||
readonly profile = computed(() => this.store.entity());
|
||||
|
||||
editing = signal(false);
|
||||
|
||||
edit() {
|
||||
this.editing.set(true);
|
||||
}
|
||||
|
||||
onEditSuccess() {
|
||||
this.store.getData();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user