valtech-components 2.0.681 → 2.0.682

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/esm2022/lib/components/molecules/image-crop/image-crop.component.mjs +174 -0
  2. package/esm2022/lib/components/molecules/image-crop/index.mjs +2 -0
  3. package/esm2022/lib/components/organisms/avatar-upload/avatar-upload.component.mjs +345 -0
  4. package/esm2022/lib/components/organisms/avatar-upload/types.mjs +15 -0
  5. package/esm2022/lib/components/templates/docs-page/docs-page.component.mjs +35 -4
  6. package/esm2022/lib/components/templates/docs-page/types.mjs +1 -1
  7. package/esm2022/lib/services/auth/auth.service.mjs +11 -2
  8. package/esm2022/lib/services/auth/types.mjs +1 -1
  9. package/esm2022/lib/services/image/image.service.mjs +244 -0
  10. package/esm2022/lib/services/image/index.mjs +3 -0
  11. package/esm2022/lib/services/image/types.mjs +13 -0
  12. package/esm2022/lib/version.mjs +2 -2
  13. package/esm2022/public-api.mjs +7 -1
  14. package/fesm2022/valtech-components.mjs +815 -7
  15. package/fesm2022/valtech-components.mjs.map +1 -1
  16. package/lib/components/molecules/image-crop/image-crop.component.d.ts +59 -0
  17. package/lib/components/molecules/image-crop/index.d.ts +1 -0
  18. package/lib/components/organisms/avatar-upload/avatar-upload.component.d.ts +82 -0
  19. package/lib/components/organisms/avatar-upload/types.d.ts +62 -0
  20. package/lib/components/templates/docs-page/docs-page.component.d.ts +3 -0
  21. package/lib/components/templates/docs-page/types.d.ts +39 -0
  22. package/lib/services/auth/auth.service.d.ts +6 -1
  23. package/lib/services/auth/types.d.ts +18 -0
  24. package/lib/services/image/image.service.d.ts +76 -0
  25. package/lib/services/image/index.d.ts +2 -0
  26. package/lib/services/image/types.d.ts +74 -0
  27. package/lib/version.d.ts +1 -1
  28. package/package.json +2 -1
  29. package/public-api.d.ts +4 -0
  30. package/src/lib/services/firebase/firebase-messaging-sw.js +134 -0
@@ -0,0 +1,59 @@
1
+ import { EventEmitter } from '@angular/core';
2
+ import { ImageCroppedEvent } from 'ngx-image-cropper';
3
+ import * as i0 from "@angular/core";
4
+ /**
5
+ * ImageCropComponent
6
+ *
7
+ * A modal-ready component for cropping images with a specified aspect ratio.
8
+ * Uses ngx-image-cropper internally and provides a simple interface.
9
+ *
10
+ * @example Inside an ion-modal
11
+ * ```html
12
+ * <ion-modal [isOpen]="showCropModal">
13
+ * <ng-template>
14
+ * <val-image-crop
15
+ * [image]="selectedFile"
16
+ * [aspectRatio]="1"
17
+ * [roundCropper]="true"
18
+ * (cropComplete)="onCropComplete($event)"
19
+ * (cancel)="showCropModal = false"
20
+ * />
21
+ * </ng-template>
22
+ * </ion-modal>
23
+ * ```
24
+ */
25
+ export declare class ImageCropComponent {
26
+ private i18n;
27
+ /** Image file to crop */
28
+ readonly image: import("@angular/core").InputSignal<File>;
29
+ /** Aspect ratio (1 for square, 16/9 for widescreen, etc.) */
30
+ readonly aspectRatio: import("@angular/core").InputSignal<number>;
31
+ /** Use round cropper (for avatars) */
32
+ readonly roundCropper: import("@angular/core").InputSignal<boolean>;
33
+ /** Resize output to specific width (0 = no resize) */
34
+ readonly resizeToWidth: import("@angular/core").InputSignal<number>;
35
+ /** i18n namespace for labels */
36
+ readonly i18nNamespace: import("@angular/core").InputSignal<string>;
37
+ /** Emitted when crop is confirmed with the cropped blob */
38
+ cropComplete: EventEmitter<Blob>;
39
+ /** Emitted when user cancels the crop */
40
+ cancel: EventEmitter<void>;
41
+ /** Emitted when image fails to load */
42
+ loadFailed: EventEmitter<void>;
43
+ /** Internal signal for cropped blob */
44
+ protected croppedBlob: import("@angular/core").WritableSignal<Blob>;
45
+ /** Computed text for cancel button */
46
+ protected cancelText: import("@angular/core").Signal<string>;
47
+ /** Computed text for confirm button */
48
+ protected confirmText: import("@angular/core").Signal<string>;
49
+ /** Computed text for title */
50
+ protected titleText: import("@angular/core").Signal<string>;
51
+ /** Handle crop event from ngx-image-cropper */
52
+ onImageCropped(event: ImageCroppedEvent): void;
53
+ /** Confirm and emit the cropped blob */
54
+ confirmCrop(): void;
55
+ /** Handle load failure */
56
+ onLoadFailed(): void;
57
+ static ɵfac: i0.ɵɵFactoryDeclaration<ImageCropComponent, never>;
58
+ static ɵcmp: i0.ɵɵComponentDeclaration<ImageCropComponent, "val-image-crop", never, { "image": { "alias": "image"; "required": true; "isSignal": true; }; "aspectRatio": { "alias": "aspectRatio"; "required": false; "isSignal": true; }; "roundCropper": { "alias": "roundCropper"; "required": false; "isSignal": true; }; "resizeToWidth": { "alias": "resizeToWidth"; "required": false; "isSignal": true; }; "i18nNamespace": { "alias": "i18nNamespace"; "required": false; "isSignal": true; }; }, { "cropComplete": "cropComplete"; "cancel": "cancel"; "loadFailed": "loadFailed"; }, never, never, true, never>;
59
+ }
@@ -0,0 +1 @@
1
+ export * from './image-crop.component';
@@ -0,0 +1,82 @@
1
+ import { ElementRef, EventEmitter } from '@angular/core';
2
+ import { AvatarUploadError, AvatarUploadMetadata, AvatarUploadResult } from './types';
3
+ import * as i0 from "@angular/core";
4
+ /**
5
+ * AvatarUploadComponent
6
+ *
7
+ * A complete avatar upload solution with:
8
+ * - Image selection from device
9
+ * - Crop modal with round preview
10
+ * - Automatic compression and thumbnail generation
11
+ * - Upload to Firebase Storage
12
+ * - Backend sync via AuthService
13
+ *
14
+ * @example Basic usage
15
+ * ```html
16
+ * <val-avatar-upload
17
+ * [props]="{
18
+ * currentUrl: user()?.avatarUrl,
19
+ * initials: 'JD',
20
+ * size: 120
21
+ * }"
22
+ * (uploaded)="onAvatarUploaded($event)"
23
+ * (error)="onError($event)"
24
+ * />
25
+ * ```
26
+ */
27
+ export declare class AvatarUploadComponent {
28
+ private imageService;
29
+ private storageService;
30
+ private authService;
31
+ private i18n;
32
+ fileInput: ElementRef<HTMLInputElement>;
33
+ /** Component configuration */
34
+ readonly props: import("@angular/core").InputSignal<AvatarUploadMetadata>;
35
+ /** Emitted after successful upload and backend sync */
36
+ uploaded: EventEmitter<AvatarUploadResult>;
37
+ /** Emitted on any error during the process */
38
+ error: EventEmitter<AvatarUploadError>;
39
+ /** Emitted when upload starts */
40
+ uploadStart: EventEmitter<void>;
41
+ protected loading: import("@angular/core").WritableSignal<boolean>;
42
+ protected showCropModal: import("@angular/core").WritableSignal<boolean>;
43
+ protected selectedFile: import("@angular/core").WritableSignal<File>;
44
+ protected previewUrl: import("@angular/core").WritableSignal<string>;
45
+ protected imageLoadError: import("@angular/core").WritableSignal<boolean>;
46
+ /** Merged config with defaults */
47
+ protected config: import("@angular/core").Signal<{
48
+ currentUrl?: string;
49
+ initials?: string;
50
+ backgroundColor: string;
51
+ size: number;
52
+ editable: boolean;
53
+ storagePath: string;
54
+ i18nNamespace: string;
55
+ maxFileSize: number;
56
+ compressQuality: number;
57
+ maxWidth: number;
58
+ thumbnailSize: number;
59
+ }>;
60
+ /** URL to display (preview takes priority over current) */
61
+ protected displayUrl: import("@angular/core").Signal<string>;
62
+ /** Aria label for edit button */
63
+ protected editButtonLabel: import("@angular/core").Signal<string>;
64
+ /** Open file picker dialog */
65
+ openFilePicker(): void;
66
+ /** Handle file selection */
67
+ onFileSelected(event: Event): void;
68
+ /** Handle crop completion */
69
+ onCropComplete(croppedBlob: Blob): Promise<void>;
70
+ /** Handle crop cancel */
71
+ onCropCancel(): void;
72
+ /** Handle crop load failure */
73
+ onCropLoadFailed(): void;
74
+ /** Handle image load error */
75
+ onImageError(): void;
76
+ /** Process cropped image and upload */
77
+ private processAndUpload;
78
+ /** Emit error event */
79
+ private emitError;
80
+ static ɵfac: i0.ɵɵFactoryDeclaration<AvatarUploadComponent, never>;
81
+ static ɵcmp: i0.ɵɵComponentDeclaration<AvatarUploadComponent, "val-avatar-upload", never, { "props": { "alias": "props"; "required": false; "isSignal": true; }; }, { "uploaded": "uploaded"; "error": "error"; "uploadStart": "uploadStart"; }, never, never, true, never>;
82
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Configuration for AvatarUploadComponent
3
+ */
4
+ export interface AvatarUploadMetadata {
5
+ /** Current avatar URL */
6
+ currentUrl?: string;
7
+ /** Initials to show when no avatar (e.g., "JD" for John Doe) */
8
+ initials?: string;
9
+ /** Background color for initials avatar */
10
+ backgroundColor?: string;
11
+ /** Avatar size in pixels (default: 100) */
12
+ size?: number;
13
+ /** Show edit button (default: true) */
14
+ editable?: boolean;
15
+ /** Storage path prefix without userId (default: 'avatars') */
16
+ storagePath?: string;
17
+ /** i18n namespace for labels (default: 'AvatarUpload') */
18
+ i18nNamespace?: string;
19
+ /** Max file size in bytes (default: 10MB) */
20
+ maxFileSize?: number;
21
+ /** Quality for compressed image 0-1 (default: 0.8) */
22
+ compressQuality?: number;
23
+ /** Max width for avatar (default: 800) */
24
+ maxWidth?: number;
25
+ /** Thumbnail size (default: 150) */
26
+ thumbnailSize?: number;
27
+ }
28
+ /**
29
+ * Result emitted after successful upload
30
+ */
31
+ export interface AvatarUploadResult {
32
+ /** Full-size avatar URL */
33
+ avatarUrl: string;
34
+ /** Thumbnail URL */
35
+ thumbnailUrl: string;
36
+ }
37
+ /**
38
+ * Error types that can occur during upload
39
+ */
40
+ export type AvatarUploadErrorType = 'invalidType' | 'fileTooLarge' | 'uploadFailed' | 'backendFailed' | 'cancelled';
41
+ /**
42
+ * Error object emitted on failure
43
+ */
44
+ export interface AvatarUploadError {
45
+ type: AvatarUploadErrorType;
46
+ message: string;
47
+ originalError?: unknown;
48
+ }
49
+ /**
50
+ * Default values
51
+ */
52
+ export declare const AVATAR_UPLOAD_DEFAULTS: {
53
+ size: number;
54
+ editable: boolean;
55
+ storagePath: string;
56
+ i18nNamespace: string;
57
+ maxFileSize: number;
58
+ compressQuality: number;
59
+ maxWidth: number;
60
+ thumbnailSize: number;
61
+ backgroundColor: string;
62
+ };
@@ -2,6 +2,7 @@ import { AfterViewInit, OnDestroy } from '@angular/core';
2
2
  import { DocsBreadcrumbMetadata } from '../../molecules/docs-breadcrumb/types';
3
3
  import { DocsNavLinksMetadata } from '../../molecules/docs-nav-links/types';
4
4
  import { DocsTocMetadata } from '../../organisms/docs-toc/types';
5
+ import { ContentReactionMetadata } from '../../molecules/content-reaction/types';
5
6
  import { DocsPageMetadata } from './types';
6
7
  import * as i0 from "@angular/core";
7
8
  /**
@@ -41,6 +42,7 @@ import * as i0 from "@angular/core";
41
42
  */
42
43
  export declare class DocsPageComponent implements AfterViewInit, OnDestroy {
43
44
  private elementRef;
45
+ private router;
44
46
  private _props;
45
47
  set props(value: DocsPageMetadata);
46
48
  get props(): DocsPageMetadata;
@@ -53,6 +55,7 @@ export declare class DocsPageComponent implements AfterViewInit, OnDestroy {
53
55
  navLinksProps: import("@angular/core").Signal<DocsNavLinksMetadata>;
54
56
  showNavLinks: import("@angular/core").Signal<boolean>;
55
57
  breadcrumbProps: import("@angular/core").Signal<DocsBreadcrumbMetadata>;
58
+ feedbackProps: import("@angular/core").Signal<ContentReactionMetadata>;
56
59
  static ɵfac: i0.ɵɵFactoryDeclaration<DocsPageComponent, never>;
57
60
  static ɵcmp: i0.ɵɵComponentDeclaration<DocsPageComponent, "val-docs-page", never, { "props": { "alias": "props"; "required": false; }; }, {}, never, ["*"], true, never>;
58
61
  }
@@ -66,6 +66,45 @@ export interface DocsPageMetadata {
66
66
  * Custom CSS class.
67
67
  */
68
68
  cssClass?: string;
69
+ /**
70
+ * Feedback/reaction widget configuration.
71
+ * When enabled, shows a "Was this page helpful?" reaction widget.
72
+ */
73
+ feedback?: DocsPageFeedbackConfig;
74
+ }
75
+ /**
76
+ * Configuration for the feedback reaction widget.
77
+ */
78
+ export interface DocsPageFeedbackConfig {
79
+ /**
80
+ * Enable the feedback widget.
81
+ */
82
+ enabled: boolean;
83
+ /**
84
+ * Entity type for categorizing feedback.
85
+ * @default 'docs-page'
86
+ */
87
+ entityType?: string;
88
+ /**
89
+ * Entity ID for this page. If not provided, derives from current route.
90
+ * @example 'components-button', 'guides-theming'
91
+ */
92
+ entityId?: string;
93
+ /**
94
+ * Custom question text. If not provided, uses i18n default.
95
+ * @example '¿Te resultó útil esta página?'
96
+ */
97
+ question?: string;
98
+ /**
99
+ * Allow anonymous (unauthenticated) feedback.
100
+ * @default true
101
+ */
102
+ allowAnonymous?: boolean;
103
+ /**
104
+ * Show optional comment field after reaction.
105
+ * @default true
106
+ */
107
+ showComment?: boolean;
69
108
  }
70
109
  /**
71
110
  * Link to another documentation page.
@@ -6,7 +6,7 @@ import { AuthStateService } from './auth-state.service';
6
6
  import { TokenService } from './token.service';
7
7
  import { AuthStorageService } from './storage.service';
8
8
  import { AuthSyncService } from './sync.service';
9
- import { SigninRequest, SigninResponse, SignupRequest, SignupResponse, VerifyEmailRequest, VerifyEmailResponse, ResendCodeRequest, ResendCodeResponse, MFAVerifyResponse, RefreshResponse, GetPermissionsResponse, GetProfileResponse, UpdateProfileRequest, UpdateProfileResponse, MFASetupResponse, MFAConfirmResponse, MFADisableResponse, ForgotPasswordRequest, ForgotPasswordResponse, ResetPasswordRequest, ResetPasswordResponse, ChangePasswordResponse, DeleteAccountResponse, SwitchOrgResponse, MFAMethod, AuthError, ValtechAuthConfig, EnableNotificationsResult, NotificationPermissionState, RegisterDeviceResult, TOTPSetupResponse, TOTPVerifySetupResponse, TOTPDisableResponse, RegenerateBackupCodesResponse, BackupCodesCountResponse, OAuthProvider, LinkedProvider, HasPasswordResponse, UpdateHandleResponse, CheckHandleResponse } from './types';
9
+ import { SigninRequest, SigninResponse, SignupRequest, SignupResponse, VerifyEmailRequest, VerifyEmailResponse, ResendCodeRequest, ResendCodeResponse, MFAVerifyResponse, RefreshResponse, GetPermissionsResponse, GetProfileResponse, UpdateProfileRequest, UpdateProfileResponse, MFASetupResponse, MFAConfirmResponse, MFADisableResponse, ForgotPasswordRequest, ForgotPasswordResponse, ResetPasswordRequest, ResetPasswordResponse, ChangePasswordResponse, DeleteAccountResponse, SwitchOrgResponse, MFAMethod, AuthError, ValtechAuthConfig, EnableNotificationsResult, NotificationPermissionState, RegisterDeviceResult, TOTPSetupResponse, TOTPVerifySetupResponse, TOTPDisableResponse, RegenerateBackupCodesResponse, BackupCodesCountResponse, OAuthProvider, LinkedProvider, HasPasswordResponse, UpdateHandleResponse, CheckHandleResponse, UpdateAvatarRequest, UpdateAvatarResponse } from './types';
10
10
  import { OAuthService } from './oauth.service';
11
11
  import { FirebaseService, MessagingService } from '../firebase';
12
12
  import { I18nService } from '../i18n';
@@ -241,6 +241,11 @@ export declare class AuthService implements OnDestroy {
241
241
  * Actualiza el perfil del usuario.
242
242
  */
243
243
  updateProfile(request: UpdateProfileRequest): Observable<UpdateProfileResponse>;
244
+ /**
245
+ * Actualiza el avatar del usuario en el backend.
246
+ * Nota: El estado local del avatar se maneja a través de getProfile().
247
+ */
248
+ updateAvatar(request: UpdateAvatarRequest): Observable<UpdateAvatarResponse>;
244
249
  /**
245
250
  * Inicia el proceso de recuperación de contraseña.
246
251
  * Envía un código al email del usuario.
@@ -453,6 +453,24 @@ export interface UpdateProfileResponse {
453
453
  operationId: string;
454
454
  updated: boolean;
455
455
  }
456
+ /**
457
+ * Request para actualizar avatar del usuario.
458
+ */
459
+ export interface UpdateAvatarRequest {
460
+ /** URL del avatar en Firebase Storage */
461
+ avatarUrl: string;
462
+ /** URL del thumbnail (opcional) */
463
+ avatarThumbnail?: string;
464
+ }
465
+ /**
466
+ * Response de actualizar avatar.
467
+ */
468
+ export interface UpdateAvatarResponse {
469
+ operationId: string;
470
+ avatarUrl: string;
471
+ avatarThumbnail?: string;
472
+ updatedAt: string;
473
+ }
456
474
  /**
457
475
  * Request para cambiar de organización activa.
458
476
  */
@@ -0,0 +1,76 @@
1
+ import { CropData, ImageCompressOptions, ImageValidateOptions, ImageValidationResult, ProcessedImage } from './types';
2
+ import * as i0 from "@angular/core";
3
+ /**
4
+ * ImageService
5
+ *
6
+ * Service for image processing including compression, thumbnails, cropping and validation.
7
+ * Uses HTML Canvas for all operations - no external dependencies.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const imageService = inject(ImageService);
12
+ *
13
+ * // Compress an image
14
+ * const compressed = await imageService.compress(file, { maxWidth: 800, quality: 0.8 });
15
+ *
16
+ * // Generate thumbnail
17
+ * const thumb = await imageService.thumbnail(file, 150);
18
+ *
19
+ * // Validate before processing
20
+ * const validation = imageService.validate(file, { maxSize: 5 * 1024 * 1024 });
21
+ * if (!validation.valid) {
22
+ * console.error(validation.message);
23
+ * }
24
+ * ```
25
+ */
26
+ export declare class ImageService {
27
+ /**
28
+ * Compress an image maintaining aspect ratio
29
+ * @param file - File or Blob to compress
30
+ * @param options - Compression options
31
+ * @returns Promise with processed image data
32
+ */
33
+ compress(file: File | Blob, options?: ImageCompressOptions): Promise<ProcessedImage>;
34
+ /**
35
+ * Generate a square thumbnail from an image
36
+ * @param file - File or Blob to process
37
+ * @param size - Thumbnail size in pixels (default: 150)
38
+ * @returns Promise with processed thumbnail
39
+ */
40
+ thumbnail(file: File | Blob, size?: number): Promise<ProcessedImage>;
41
+ /**
42
+ * Crop an image with specific coordinates
43
+ * @param file - File or Blob to crop
44
+ * @param cropData - Crop coordinates and dimensions
45
+ * @param options - Optional compression options for output
46
+ * @returns Promise with cropped image
47
+ */
48
+ crop(file: File | Blob, cropData: CropData, options?: ImageCompressOptions): Promise<ProcessedImage>;
49
+ /**
50
+ * Validate an image file before processing
51
+ * @param file - File to validate
52
+ * @param options - Validation options
53
+ * @returns Validation result with error details if invalid
54
+ */
55
+ validate(file: File, options?: ImageValidateOptions): ImageValidationResult;
56
+ /**
57
+ * Validate image dimensions (async - requires loading image)
58
+ * @param file - File to validate
59
+ * @param options - Validation options with minWidth/minHeight
60
+ * @returns Promise with validation result
61
+ */
62
+ validateDimensions(file: File, options: Pick<ImageValidateOptions, 'minWidth' | 'minHeight'>): Promise<ImageValidationResult>;
63
+ /**
64
+ * Convert a Blob/File to a data URL
65
+ */
66
+ toDataUrl(file: File | Blob): Promise<string>;
67
+ /**
68
+ * Convert a data URL to a Blob
69
+ */
70
+ dataUrlToBlob(dataUrl: string): Blob;
71
+ private loadImage;
72
+ private calculateDimensions;
73
+ private canvasToBlob;
74
+ static ɵfac: i0.ɵɵFactoryDeclaration<ImageService, never>;
75
+ static ɵprov: i0.ɵɵInjectableDeclaration<ImageService>;
76
+ }
@@ -0,0 +1,2 @@
1
+ export * from './image.service';
2
+ export * from './types';
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Options for image compression
3
+ */
4
+ export interface ImageCompressOptions {
5
+ /** Maximum width in pixels (default: 800) */
6
+ maxWidth?: number;
7
+ /** Maximum height in pixels (default: 800) */
8
+ maxHeight?: number;
9
+ /** Quality 0-1 (default: 0.8) */
10
+ quality?: number;
11
+ /** Output MIME type (default: 'image/jpeg') */
12
+ mimeType?: 'image/jpeg' | 'image/png' | 'image/webp';
13
+ }
14
+ /**
15
+ * Options for image validation
16
+ */
17
+ export interface ImageValidateOptions {
18
+ /** Maximum file size in bytes (default: 10MB) */
19
+ maxSize?: number;
20
+ /** Allowed MIME types (default: ['image/jpeg', 'image/png', 'image/webp', 'image/gif']) */
21
+ allowedTypes?: string[];
22
+ /** Minimum width in pixels */
23
+ minWidth?: number;
24
+ /** Minimum height in pixels */
25
+ minHeight?: number;
26
+ }
27
+ /**
28
+ * Result of image validation
29
+ */
30
+ export interface ImageValidationResult {
31
+ valid: boolean;
32
+ error?: 'invalidType' | 'fileTooLarge' | 'imageTooSmall';
33
+ message?: string;
34
+ }
35
+ /**
36
+ * Processed image result
37
+ */
38
+ export interface ProcessedImage {
39
+ /** Processed image as Blob */
40
+ blob: Blob;
41
+ /** Data URL for preview */
42
+ dataUrl: string;
43
+ /** Final width in pixels */
44
+ width: number;
45
+ /** Final height in pixels */
46
+ height: number;
47
+ /** File size in bytes */
48
+ size: number;
49
+ }
50
+ /**
51
+ * Crop data for manual cropping
52
+ */
53
+ export interface CropData {
54
+ /** X position of crop area */
55
+ x: number;
56
+ /** Y position of crop area */
57
+ y: number;
58
+ /** Width of crop area */
59
+ width: number;
60
+ /** Height of crop area */
61
+ height: number;
62
+ }
63
+ /**
64
+ * Default values for image processing
65
+ */
66
+ export declare const IMAGE_DEFAULTS: {
67
+ maxWidth: number;
68
+ maxHeight: number;
69
+ quality: number;
70
+ mimeType: "image/jpeg";
71
+ maxSize: number;
72
+ allowedTypes: string[];
73
+ thumbnailSize: number;
74
+ };
package/lib/version.d.ts CHANGED
@@ -2,4 +2,4 @@
2
2
  * Current version of valtech-components.
3
3
  * This is automatically updated during the publish process.
4
4
  */
5
- export declare const VERSION = "2.0.681";
5
+ export declare const VERSION = "2.0.682";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "valtech-components",
3
- "version": "2.0.681",
3
+ "version": "2.0.682",
4
4
  "private": false,
5
5
  "bin": {
6
6
  "valtech-firebase-config": "./src/lib/services/firebase/scripts/generate-sw-config.js"
@@ -32,6 +32,7 @@
32
32
  "dependencies": {
33
33
  "@capacitor/browser": "^6.0.3",
34
34
  "ng-otp-input": "^1.9.3",
35
+ "ngx-image-cropper": "^9.0.0",
35
36
  "tslib": "^2.3.0"
36
37
  },
37
38
  "sideEffects": false,
package/public-api.d.ts CHANGED
@@ -169,6 +169,7 @@ export * from './lib/components/molecules/username-input/username-input.componen
169
169
  export * from './lib/components/molecules/username-input/types';
170
170
  export * from './lib/components/molecules/linked-providers/linked-providers.component';
171
171
  export * from './lib/components/molecules/linked-providers/types';
172
+ export * from './lib/components/molecules/image-crop/image-crop.component';
172
173
  export * from './lib/components/organisms/article/article.component';
173
174
  export * from './lib/components/organisms/article/types';
174
175
  export * from './lib/components/organisms/banner/banner.component';
@@ -214,6 +215,8 @@ export * from './lib/components/organisms/terminal-404/terminal-404.component';
214
215
  export * from './lib/components/organisms/terminal-404/types';
215
216
  export * from './lib/components/organisms/bottom-nav/bottom-nav.component';
216
217
  export * from './lib/components/organisms/bottom-nav/types';
218
+ export * from './lib/components/organisms/avatar-upload/avatar-upload.component';
219
+ export * from './lib/components/organisms/avatar-upload/types';
217
220
  export * from './lib/components/templates/layout/layout.component';
218
221
  export * from './lib/components/templates/simple/simple.component';
219
222
  export * from './lib/components/templates/simple/types';
@@ -251,6 +254,7 @@ export * from './lib/services/app-config';
251
254
  export * from './lib/services/presets';
252
255
  export * from './lib/services/skeleton';
253
256
  export * from './lib/services/pagination';
257
+ export * from './lib/services/image';
254
258
  export * from './lib/services/ads';
255
259
  export * from './lib/components/molecules/ad-slot/ad-slot.component';
256
260
  export * from './lib/services/feedback';
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Firebase Messaging Service Worker
3
+ *
4
+ * Service Worker estático para Firebase Cloud Messaging.
5
+ * Carga la configuración dinámicamente desde /firebase-config.js.
6
+ *
7
+ * CONFIGURACIÓN:
8
+ * 1. Crea firebase.config.json con tu configuración de Firebase
9
+ * 2. Ejecuta: npm run generate:firebase-config
10
+ * Esto genera /firebase-config.js con: self.FIREBASE_CONFIG = {...}
11
+ * 3. Agrega este SW y firebase-config.js a los assets de angular.json
12
+ *
13
+ * Ver README.md para documentación completa.
14
+ */
15
+
16
+ // Importar Firebase scripts
17
+ importScripts('https://www.gstatic.com/firebasejs/10.7.0/firebase-app-compat.js');
18
+ importScripts('https://www.gstatic.com/firebasejs/10.7.0/firebase-messaging-compat.js');
19
+
20
+ // Importar configuración desde archivo externo (generado en build)
21
+ // Este archivo define: self.FIREBASE_CONFIG = { ... }
22
+ try {
23
+ importScripts('/firebase-config.js');
24
+ } catch (e) {
25
+ console.error('[SW] No se pudo cargar firebase-config.js:', e);
26
+ }
27
+
28
+ // Verificar que la configuración existe
29
+ if (!self.FIREBASE_CONFIG) {
30
+ console.error('[SW] FIREBASE_CONFIG no está definido.');
31
+ console.error('[SW] Ejecuta: npm run generate:firebase-config');
32
+ } else {
33
+ // Inicializar Firebase
34
+ firebase.initializeApp(self.FIREBASE_CONFIG);
35
+
36
+ // Obtener instancia de messaging
37
+ const messaging = firebase.messaging();
38
+
39
+ /**
40
+ * Handler para mensajes en background.
41
+ */
42
+ messaging.onBackgroundMessage((payload) => {
43
+ console.log('[SW] Mensaje recibido en background:', payload);
44
+
45
+ const notificationTitle = payload.notification?.title || 'Nueva notificación';
46
+ const notificationOptions = {
47
+ body: payload.notification?.body || '',
48
+ icon: payload.notification?.icon || '/assets/icon/favicon.ico',
49
+ image: payload.notification?.image,
50
+ badge: '/assets/icon/badge.png',
51
+ tag: payload.messageId || 'default',
52
+ data: {
53
+ ...payload.data,
54
+ messageId: payload.messageId,
55
+ title: notificationTitle,
56
+ body: payload.notification?.body,
57
+ },
58
+ vibrate: [200, 100, 200],
59
+ requireInteraction: payload.data?.require_interaction === 'true',
60
+ };
61
+
62
+ return self.registration.showNotification(notificationTitle, notificationOptions);
63
+ });
64
+
65
+ /**
66
+ * Handler para clicks en notificaciones.
67
+ */
68
+ self.addEventListener('notificationclick', (event) => {
69
+ console.log('[SW] Click en notificación:', event);
70
+ event.notification.close();
71
+
72
+ const data = event.notification.data || {};
73
+ let targetUrl = '/';
74
+
75
+ if (data.route) {
76
+ targetUrl = data.route;
77
+ } else if (data.url) {
78
+ targetUrl = data.url;
79
+ }
80
+
81
+ if (data.query_params) {
82
+ const separator = targetUrl.includes('?') ? '&' : '?';
83
+ targetUrl += separator + data.query_params;
84
+ }
85
+
86
+ const notificationPayload = {
87
+ type: 'NOTIFICATION_CLICK',
88
+ notification: {
89
+ title: data.title,
90
+ body: data.body,
91
+ data: data,
92
+ messageId: data.messageId,
93
+ },
94
+ };
95
+
96
+ event.waitUntil(
97
+ clients
98
+ .matchAll({ type: 'window', includeUncontrolled: true })
99
+ .then((clientList) => {
100
+ // Siempre enviar postMessage para que la app pueda reaccionar
101
+ for (const client of clientList) {
102
+ client.postMessage(notificationPayload);
103
+ }
104
+
105
+ // Navegar si hay route o url
106
+ if (targetUrl !== '/') {
107
+ for (const client of clientList) {
108
+ if ('navigate' in client) {
109
+ return client.navigate(targetUrl).then((c) => c?.focus());
110
+ }
111
+ }
112
+ // Si no hay cliente abierto, abrir nueva ventana
113
+ if (clients.openWindow) {
114
+ return clients.openWindow(targetUrl);
115
+ }
116
+ }
117
+
118
+ // Solo hacer focus si no hay navegación
119
+ for (const client of clientList) {
120
+ if ('focus' in client) {
121
+ return client.focus();
122
+ }
123
+ }
124
+ if (clients.openWindow) {
125
+ return clients.openWindow('/');
126
+ }
127
+ })
128
+ );
129
+ });
130
+
131
+ self.addEventListener('notificationclose', (event) => {
132
+ console.log('[SW] Notificación cerrada:', event.notification.tag);
133
+ });
134
+ }