@@ -42,12 +48,32 @@
}
diff --git a/packages/album/ui-shared/src/lib/components/photo-viewer/photo-viewer.component.scss b/packages/album/ui-shared/src/lib/components/photo-viewer/photo-viewer.component.scss
index 2e7d5c03..47c6dd10 100644
--- a/packages/album/ui-shared/src/lib/components/photo-viewer/photo-viewer.component.scss
+++ b/packages/album/ui-shared/src/lib/components/photo-viewer/photo-viewer.component.scss
@@ -17,6 +17,7 @@
figure {
margin: 0;
width: 100%;
+ max-height: 100%;
position: relative;
display: flex;
flex-direction: column;
@@ -44,16 +45,12 @@
position: absolute;
transform: translate(-50%, -50%);
display: flex;
- align-items: center;
+ align-items: flex-end;
justify-content: flex-end;
- color: #f9e000;
- opacity: 0.2;
transition: opacity 250ms ease-in-out;
+ color: #f9e000;
width: 64px;
height: 64px;
- &:hover {
- opacity: 1;
- }
}
}
@@ -61,6 +58,20 @@
gap: 1em;
padding: 0.6em;
display: flex;
- justify-content: flex-end;
+ flex-flow: row wrap;
+ justify-content: space-between;
+ align-items: center;
+
+ small {
+ flex: 1;
+ line-height: 140%;
+ font-weight: normal;
+ }
+
+ .mat-mdc-icon-button {
+ devmx-icon {
+ display: flex;
+ }
+ }
}
}
diff --git a/packages/album/ui-shared/src/lib/components/photo-viewer/photo-viewer.component.ts b/packages/album/ui-shared/src/lib/components/photo-viewer/photo-viewer.component.ts
index 572669f6..82da843d 100644
--- a/packages/album/ui-shared/src/lib/components/photo-viewer/photo-viewer.component.ts
+++ b/packages/album/ui-shared/src/lib/components/photo-viewer/photo-viewer.component.ts
@@ -1,9 +1,12 @@
import { Authentication, Photo, UserTag } from '@devmx/shared-api-interfaces';
-import { MatTooltipModule } from '@angular/material/tooltip';
+import { MatTooltip, MatTooltipModule } from '@angular/material/tooltip';
import { DIALOG_DATA, DialogRef } from '@angular/cdk/dialog';
import { IconComponent } from '@devmx/shared-ui-global/icon';
import { MatButtonModule } from '@angular/material/button';
+import { ClipboardModule } from '@angular/cdk/clipboard';
import { MatMenuModule } from '@angular/material/menu';
+import { arrayEquals } from '@devmx/shared-util-data';
+import { concat, of, Subject, tap, timer } from 'rxjs';
import { PhotoViewerData } from './photo-viewer-data';
import { detectTaggingIntent } from '../utils';
import {
@@ -12,6 +15,7 @@ import {
Component,
viewChild,
ElementRef,
+ viewChildren,
AfterViewInit,
ChangeDetectorRef,
ChangeDetectionStrategy,
@@ -27,76 +31,146 @@ import {
styleUrl: './photo-viewer.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [provideTagUserService()],
- imports: [MatTooltipModule, MatButtonModule, MatMenuModule, IconComponent],
+ imports: [
+ MatTooltipModule,
+ MatButtonModule,
+ MatMenuModule,
+ ClipboardModule,
+ IconComponent,
+ ],
})
export class PhotoViewerComponent implements AfterViewInit {
tagUser = inject
(TagUserService);
- cdr = inject(ChangeDetectorRef);
+ #cdr = inject(ChangeDetectorRef);
- ref = inject>(DialogRef);
+ #ref = inject>(DialogRef);
data = inject(DIALOG_DATA);
- originalTags: UserTag[] = [];
+ tooltips = viewChildren(MatTooltip);
+
+ #originalTags: UserTag[] = [];
imageRef = viewChild.required>('imageRef');
- image!: HTMLImageElement;
+ keepEquals = signal(true);
- hasUpdates = signal(false);
+ shareLabel = signal('Compartilhar');
- hasTagWithMe(user: Authentication) {
- return this.data.photo.tags?.some((tag) => tag.user.id === user.id);
+ #photo = new Subject();
+ photo$ = this.#photo.asObservable();
+
+ #updated = new Subject();
+ updated$ = this.#updated.asObservable();
+
+ updated(photo: Photo) {
+ this.#photo.next(photo);
+ }
+
+ update(photo: Photo) {
+ this.#updated.next(photo);
+ }
+
+ share(refTooltip: MatTooltip) {
+ const set = (label: string) => {
+ return tap(() => this.shareLabel.set(label));
+ };
+
+ concat(
+ timer(500),
+ of('').pipe(set('Link copiado')),
+ of('').pipe(tap(() => refTooltip.show())),
+ timer(2000),
+ of('').pipe(tap(() => refTooltip.hide())),
+ timer(1000),
+ of('').pipe(set('Compartilhar'))
+ ).subscribe();
}
removeMyTag(user: Authentication) {
this.data.photo.tags = this.data.photo.tags?.filter(
(tag) => tag.user.id !== user.id
);
- const hasUpdates = this.arraysAreEqual(
- this.originalTags,
+
+ const keepEquals = arrayEquals(
+ this.#originalTags,
this.data.photo.tags ?? []
);
- this.hasUpdates.set(!hasUpdates);
+
+ this.keepEquals.set(keepEquals);
}
ngAfterViewInit() {
- this.originalTags = [...(this.data.photo.tags ?? [])];
+ this.#originalTags = [...(this.data.photo.tags ?? [])];
+
+ this.photo$.subscribe((photo) => {
+ this.data.photo = photo;
+ this.#originalTags = [...(this.data.photo.tags ?? [])];
+ this.#cdr.detectChanges();
+
+ const keepEquals = arrayEquals(
+ this.#originalTags,
+ this.data.photo.tags ?? []
+ );
- this.image = this.imageRef().nativeElement;
+ this.keepEquals.set(keepEquals);
+ });
+
+ const image = this.imageRef().nativeElement;
- this.image.oncontextmenu = (ev) => ev.preventDefault();
+ image.oncontextmenu = (ev) => ev.preventDefault();
- detectTaggingIntent(this.image).subscribe(({ x, y }) => {
- const offsetX = x * this.image.width;
- const offsetY = y * this.image.height;
+ detectTaggingIntent(image).subscribe((position) => {
+ const offsetX = position.x * image.width;
+ const offsetY = position.y * image.height;
- const closed$ = this.tagUser.open(this.image, offsetX, offsetY).closed;
+ const except = this.data.photo.tags?.map((tag) => tag.user) ?? [];
- closed$.subscribe((userRef) => {
- if (userRef && this.data.photo.tags) {
- const { id, displayName, name } = userRef;
+ const closed$ = this.tagUser.open({
+ target: image,
+ data: { except },
+ offsetX,
+ offsetY,
+ }).closed;
+
+ closed$.subscribe((ref) => {
+ if (ref) {
+ const { id, displayName, name } = ref;
const user = { id, displayName, name };
- this.data.photo.tags.push({ x: x * 100, y: y * 100, user });
- const hasUpdates = this.arraysAreEqual(
- this.originalTags,
- this.data.photo.tags
+
+ const x = position.x * 100;
+ const y = position.y * 100;
+
+ this.data.photo.tags?.push({ x, y, user });
+ this.#cdr.detectChanges();
+
+ const keepEquals = arrayEquals(
+ this.#originalTags,
+ this.data.photo.tags ?? []
);
- this.hasUpdates.set(!hasUpdates);
- this.cdr.detectChanges();
+
+ this.keepEquals.set(keepEquals);
}
});
});
}
- arraysAreEqual(a: T[], b: T[]) {
- if (a.length !== b.length) return false;
+ toggleTags() {
+ if (!this.data.photo.tags?.length) {
+ return;
+ }
- for (let i = 0; i < a.length; i++) {
- if (a[i] !== b[i]) return false;
+ for (const tooltip of this.tooltips()) {
+ tooltip.toggle();
}
+ }
+
+ hasTagWithMe(user: Authentication) {
+ return this.data.photo.tags?.some((tag) => tag.user.id === user.id);
+ }
- return true;
+ close() {
+ this.#ref.close();
}
}
diff --git a/packages/album/ui-shared/src/lib/components/photo-viewer/photo-viewer.service.ts b/packages/album/ui-shared/src/lib/components/photo-viewer/photo-viewer.service.ts
index 41f0baf4..f15d1191 100644
--- a/packages/album/ui-shared/src/lib/components/photo-viewer/photo-viewer.service.ts
+++ b/packages/album/ui-shared/src/lib/components/photo-viewer/photo-viewer.service.ts
@@ -7,14 +7,30 @@ export class PhotoViewerService {
constructor(private dialog: Dialog) {}
open(data: PhotoViewerData) {
- const width = `${data.photo.width}px`;
- const height = `${data.photo.height}px`;
- const disableClose = true;
+ const photoViewer = this.dialog.open<
+ Photo,
+ PhotoViewerData,
+ PhotoViewerComponent
+ >(PhotoViewerComponent, {
+ width: `${data.photo.width}px`,
+ height: `${data.photo.height}px`,
+ disableClose: true,
+ hasBackdrop: true,
+ maxHeight: `80%`,
+ maxWidth: `80%`,
+ data,
+ });
- return this.dialog.open(
- PhotoViewerComponent,
- { data, width, height, disableClose }
- );
+ const component = photoViewer.componentInstance;
+
+ if (!component) {
+ throw `Erro ao criar instância PhotoViewer`;
+ }
+
+ return {
+ updated$: component.updated$,
+ updated: (value: Photo) => component.updated(value),
+ };
}
}
diff --git a/packages/album/ui-shared/src/lib/components/tag-user/tag-user-data.ts b/packages/album/ui-shared/src/lib/components/tag-user/tag-user-data.ts
new file mode 100644
index 00000000..aa89f108
--- /dev/null
+++ b/packages/album/ui-shared/src/lib/components/tag-user/tag-user-data.ts
@@ -0,0 +1,5 @@
+import { UserRef } from '@devmx/shared-api-interfaces';
+
+export interface TagUserData {
+ except?: UserRef[];
+}
diff --git a/packages/album/ui-shared/src/lib/components/tag-user/tag-user-options.ts b/packages/album/ui-shared/src/lib/components/tag-user/tag-user-options.ts
new file mode 100644
index 00000000..59acfebf
--- /dev/null
+++ b/packages/album/ui-shared/src/lib/components/tag-user/tag-user-options.ts
@@ -0,0 +1,8 @@
+import { TagUserData } from './tag-user-data';
+
+export interface TagUserOptions {
+ target: HTMLElement;
+ offsetX?: number;
+ offsetY?: number;
+ data?: TagUserData;
+}
diff --git a/packages/album/ui-shared/src/lib/components/tag-user/tag-user.component.ts b/packages/album/ui-shared/src/lib/components/tag-user/tag-user.component.ts
index fb04bb8c..6dc4ab51 100644
--- a/packages/album/ui-shared/src/lib/components/tag-user/tag-user.component.ts
+++ b/packages/album/ui-shared/src/lib/components/tag-user/tag-user.component.ts
@@ -1,11 +1,15 @@
import { inject, Component, ChangeDetectionStrategy } from '@angular/core';
import { SearchUserComponent } from '@devmx/account-ui-shared';
+import { DIALOG_DATA, DialogRef } from '@angular/cdk/dialog';
import { UserRef } from '@devmx/shared-api-interfaces';
-import { DialogRef } from '@angular/cdk/dialog';
+import { TagUserData } from './tag-user-data';
@Component({
selector: 'devmx-tag-user',
- template: ``,
+ template: ``,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [SearchUserComponent],
styles: `
@@ -19,4 +23,6 @@ import { DialogRef } from '@angular/cdk/dialog';
})
export class TagUserComponent {
dialogRef = inject>(DialogRef);
+
+ data = inject(DIALOG_DATA);
}
diff --git a/packages/album/ui-shared/src/lib/components/tag-user/tag-user.service.ts b/packages/album/ui-shared/src/lib/components/tag-user/tag-user.service.ts
index ef233baf..cdb76f42 100644
--- a/packages/album/ui-shared/src/lib/components/tag-user/tag-user.service.ts
+++ b/packages/album/ui-shared/src/lib/components/tag-user/tag-user.service.ts
@@ -1,12 +1,14 @@
import { TagUserComponent } from './tag-user.component';
import { UserRef } from '@devmx/shared-api-interfaces';
+import { TagUserOptions } from './tag-user-options';
import { Overlay } from '@angular/cdk/overlay';
+import { TagUserData } from './tag-user-data';
import { Dialog } from '@angular/cdk/dialog';
export class TagUserService {
constructor(private overlay: Overlay, private dialog: Dialog) {}
- open(target: HTMLElement, offsetX = 0, offsetY = 0) {
+ open({ target, offsetX = 0, offsetY = 0, data }: TagUserOptions) {
const panelClass = 'tag-user-position';
const positionStrategy = this.overlay
@@ -24,9 +26,10 @@ export class TagUserService {
},
]);
- return this.dialog.open(TagUserComponent, {
- positionStrategy,
- });
+ return this.dialog.open(
+ TagUserComponent,
+ { positionStrategy, data }
+ );
}
}
diff --git a/packages/event/domain/src/server/use-cases/update-event.ts b/packages/event/domain/src/server/use-cases/update-event.ts
index 0936bf06..4c669589 100644
--- a/packages/event/domain/src/server/use-cases/update-event.ts
+++ b/packages/event/domain/src/server/use-cases/update-event.ts
@@ -3,7 +3,7 @@ import { Event, UseCase } from '@devmx/shared-api-interfaces';
import { EventsService } from '../services';
import { UpdateEvent } from '../../lib/dtos';
-export class UpdateEventUseCase implements UseCase {
+export class UpdateEventUseCase implements UseCase {
constructor(private eventsService: EventsService) {}
async execute(data: UpdateEvent) {
diff --git a/packages/event/feature-shell/src/lib/containers/events/events.container.html b/packages/event/feature-shell/src/lib/containers/events/events.container.html
index 31519fb2..920c5fd8 100644
--- a/packages/event/feature-shell/src/lib/containers/events/events.container.html
+++ b/packages/event/feature-shell/src/lib/containers/events/events.container.html
@@ -1,15 +1,11 @@
@defer (on timer(500ms)) {
diff --git a/packages/event/feature-shell/src/lib/containers/events/events.container.scss b/packages/event/feature-shell/src/lib/containers/events/events.container.scss
index b71ab8d2..528d546e 100644
--- a/packages/event/feature-shell/src/lib/containers/events/events.container.scss
+++ b/packages/event/feature-shell/src/lib/containers/events/events.container.scss
@@ -1,19 +1,16 @@
:host {
flex: 1;
display: flex;
+ padding-top: 0.6em;
flex-direction: column;
header {
+ gap: 0.4em;
display: flex;
flex-flow: row wrap;
+ align-items: baseline;
justify-content: space-between;
padding: 0.4em 0.8em;
-
- div {
- gap: 1em;
- display: flex;
- align-items: baseline;
- }
}
.events-container {
diff --git a/packages/learn/domain/src/server/use-cases/update-skill.ts b/packages/learn/domain/src/server/use-cases/update-skill.ts
index 007e1313..85ec3913 100644
--- a/packages/learn/domain/src/server/use-cases/update-skill.ts
+++ b/packages/learn/domain/src/server/use-cases/update-skill.ts
@@ -1,7 +1,9 @@
import { UseCase, Skill, EditableSkill } from '@devmx/shared-api-interfaces';
import { SkillsService } from '../services';
-export class UpdateSkillUseCase implements UseCase {
+export class UpdateSkillUseCase
+ implements UseCase
+{
constructor(private skillsService: SkillsService) {}
async execute(data: EditableSkill) {
diff --git a/packages/shared/api-interfaces/src/lib/interfaces/update.ts b/packages/shared/api-interfaces/src/lib/interfaces/update.ts
index 7b8978d2..1518c94d 100644
--- a/packages/shared/api-interfaces/src/lib/interfaces/update.ts
+++ b/packages/shared/api-interfaces/src/lib/interfaces/update.ts
@@ -3,5 +3,8 @@ import { Entity } from './entity';
import { Observable } from 'rxjs';
export interface Update {
- update(id: string, data: EditableEntity): Observable | Promise;
+ update(
+ id: string,
+ data: EditableEntity
+ ): Observable | Promise;
}
diff --git a/packages/shared/api-interfaces/src/server/dtos/send-mail.ts b/packages/shared/api-interfaces/src/server/dtos/send-mail.ts
index 5a2de179..e8e8d7f8 100644
--- a/packages/shared/api-interfaces/src/server/dtos/send-mail.ts
+++ b/packages/shared/api-interfaces/src/server/dtos/send-mail.ts
@@ -2,5 +2,13 @@ import { SendMailOptions } from 'nodemailer';
export type SendMail = Pick<
SendMailOptions,
- 'from' | 'html' | 'replyTo' | 'subject' | 'text' | 'to'
+ | 'attachDataUrls'
+ | 'attachments'
+ | 'replyTo'
+ | 'subject'
+ | 'text'
+ | 'from'
+ | 'html'
+ | 'amp'
+ | 'to'
>;
diff --git a/packages/shared/api-interfaces/src/server/envs/env.ts b/packages/shared/api-interfaces/src/server/envs/env.ts
index d10f3ca9..e6c2123d 100644
--- a/packages/shared/api-interfaces/src/server/envs/env.ts
+++ b/packages/shared/api-interfaces/src/server/envs/env.ts
@@ -1,6 +1,8 @@
export abstract class Env {
abstract production: boolean;
+ abstract origin: string;
+
abstract db: {
name: string;
host: string;
diff --git a/packages/shared/api-interfaces/src/server/index.ts b/packages/shared/api-interfaces/src/server/index.ts
index 42593050..eec75b61 100644
--- a/packages/shared/api-interfaces/src/server/index.ts
+++ b/packages/shared/api-interfaces/src/server/index.ts
@@ -2,3 +2,4 @@ export * from './interfaces';
export * from './ports';
export * from './types';
export * from './envs';
+export * from './dtos';
diff --git a/packages/shared/api-interfaces/src/server/ports/entity.service.ts b/packages/shared/api-interfaces/src/server/ports/entity.service.ts
index cad986b2..6dfb82e0 100644
--- a/packages/shared/api-interfaces/src/server/ports/entity.service.ts
+++ b/packages/shared/api-interfaces/src/server/ports/entity.service.ts
@@ -24,7 +24,7 @@ export abstract class EntityService
value: T[P]
): Promise;
- abstract update(id: string, data: EditableEntity): Promise;
+ abstract update(id: string, data: EditableEntity): Promise;
abstract delete(id: string): Promise;
}
diff --git a/packages/shared/data-source/src/lib/infrastructure/mongo.service.ts b/packages/shared/data-source/src/lib/infrastructure/mongo.service.ts
index 9c7332f6..2cb5664f 100644
--- a/packages/shared/data-source/src/lib/infrastructure/mongo.service.ts
+++ b/packages/shared/data-source/src/lib/infrastructure/mongo.service.ts
@@ -87,12 +87,10 @@ export abstract class MongoService
async update(id: string, data: EditableEntity) {
const value = this.applyEditableParser(data);
+ await this.entityModel.findOneAndUpdate({ _id: id }, value).exec();
try {
- const updated = await this.entityModel
- .findOneAndUpdate({ _id: id }, value)
- .exec();
- return updated?.toJSON() as T;
+ return this.findOne(id)
} catch (err) {
if (err instanceof Error) {
throw err;
diff --git a/packages/shared/data-source/src/lib/utils/create-schema.ts b/packages/shared/data-source/src/lib/utils/create-schema.ts
index 64c3e281..bfda45bb 100644
--- a/packages/shared/data-source/src/lib/utils/create-schema.ts
+++ b/packages/shared/data-source/src/lib/utils/create-schema.ts
@@ -28,10 +28,10 @@ export function createSchema(target: new () => T): Schema {
return this._id.toString();
});
- // schema.set('timestamps', {
- // createdAt: true,
- // updatedAt: true,
- // });
+ schema.set('timestamps', {
+ createdAt: true,
+ updatedAt: true,
+ });
schema.set('toJSON', {
virtuals: true,
diff --git a/packages/shared/ui-global/icon/src/lib/types/icon.ts b/packages/shared/ui-global/icon/src/lib/types/icon.ts
index 6b30b31a..a910329f 100644
--- a/packages/shared/ui-global/icon/src/lib/types/icon.ts
+++ b/packages/shared/ui-global/icon/src/lib/types/icon.ts
@@ -103,6 +103,7 @@ type Root =
| 'send'
| 'sent'
| 'settings'
+ | 'share'
| 'slash'
| 'star-check'
| 'star-close'
diff --git a/packages/shared/ui-global/sort/src/lib/sort-direction/sort-direction.component.html b/packages/shared/ui-global/sort/src/lib/sort-direction/sort-direction.component.html
index 7edf6056..24e10a76 100644
--- a/packages/shared/ui-global/sort/src/lib/sort-direction/sort-direction.component.html
+++ b/packages/shared/ui-global/sort/src/lib/sort-direction/sort-direction.component.html
@@ -1,5 +1,13 @@
-{{ current() === 'asc' ? ascText() : descText() }}
+
+ @for (direction of directions; track direction.value) {
+
+ {{ direction.viewValue }}
+
+ }
+
+
+
diff --git a/packages/shared/ui-global/sort/src/lib/sort-direction/sort-direction.component.ts b/packages/shared/ui-global/sort/src/lib/sort-direction/sort-direction.component.ts
index 782439f5..b5e3ebf5 100644
--- a/packages/shared/ui-global/sort/src/lib/sort-direction/sort-direction.component.ts
+++ b/packages/shared/ui-global/sort/src/lib/sort-direction/sort-direction.component.ts
@@ -1,32 +1,38 @@
-import { NgClass } from '@angular/common';
-import { Component, input, output, signal } from '@angular/core';
-import { MatButtonModule } from '@angular/material/button';
+import { ChangeDetectionStrategy, Component, output } from '@angular/core';
import { SortDirection } from '@devmx/shared-api-interfaces';
-import { IconComponent } from '@devmx/shared-ui-global/icon';
+import { FormOption } from '@devmx/shared-ui-global/forms';
+import { MatChipsModule } from '@angular/material/chips';
@Component({
exportAs: 'sortDirection',
selector: 'devmx-sort-direction',
templateUrl: './sort-direction.component.html',
styleUrl: './sort-direction.component.scss',
- imports: [MatButtonModule, IconComponent, NgClass],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ imports: [MatChipsModule],
})
export class SortDirectionComponent {
- ascText = input('');
+ sortChange = output();
- descText = input('');
+ directions: FormOption[] = [
+ { value: 'asc', viewValue: 'ASC' },
+ { value: 'desc', viewValue: 'DESC' },
+ ];
+ // ascText = input('');
- sortChange = output();
+ // descText = input('');
+
+ // sortChange = output();
- current = signal('asc');
+ // current = signal('asc');
- toggle() {
- if (this.current() === 'asc') {
- this.current.set('desc');
- } else {
- this.current.set('asc');
- }
+ // toggle() {
+ // if (this.current() === 'asc') {
+ // this.current.set('desc');
+ // } else {
+ // this.current.set('asc');
+ // }
- this.sortChange.emit(this.current());
- }
+ // this.sortChange.emit(this.current());
+ // }
}
diff --git a/packages/shared/util-data/src/lib/utils/array-equals.ts b/packages/shared/util-data/src/lib/utils/array-equals.ts
new file mode 100644
index 00000000..16351c18
--- /dev/null
+++ b/packages/shared/util-data/src/lib/utils/array-equals.ts
@@ -0,0 +1,9 @@
+export function arrayEquals(a: T[], b: T[]) {
+ if (a.length !== b.length) return false;
+
+ for (let i = 0; i < a.length; i++) {
+ if (a[i] !== b[i]) return false;
+ }
+
+ return true;
+}
diff --git a/packages/shared/util-data/src/lib/utils/create-mail.ts b/packages/shared/util-data/src/lib/utils/create-mail.ts
index fdb6b8e0..9d257ebf 100644
--- a/packages/shared/util-data/src/lib/utils/create-mail.ts
+++ b/packages/shared/util-data/src/lib/utils/create-mail.ts
@@ -1,3 +1,5 @@
+import { SendMail } from '@devmx/shared-api-interfaces/server';
+
export function createMail(
to: string,
html: string,
@@ -8,3 +10,38 @@ export function createMail(
return { from, to, subject, html };
}
+
+export type CreateMailTemplateOptions = Omit<
+ SendMail,
+ 'to' | 'html' | 'subject'
+> & {
+ withFooter?: boolean;
+};
+
+export function createMailTemplate(
+ to: string,
+ html: string,
+ subject = `DevParana.mx`,
+ extraOptions: CreateMailTemplateOptions = { }
+): SendMail {
+ const logo = `
+
+ `;
+
+ return {
+ to,
+ ...extraOptions,
+ html: extraOptions.withFooter ? html + logo : html,
+ attachments: [
+ extraOptions.withFooter ? { content: logo, filename: 'logo.svg' } : {},
+ ],
+ subject: subject === 'DevParana.mx' ? subject : `${subject} - DevParana.mx`,
+ };
+}
diff --git a/packages/shared/util-data/src/lib/utils/index.ts b/packages/shared/util-data/src/lib/utils/index.ts
index ee357be4..7fd89553 100644
--- a/packages/shared/util-data/src/lib/utils/index.ts
+++ b/packages/shared/util-data/src/lib/utils/index.ts
@@ -1,3 +1,4 @@
+export * from './array-equals';
export * from './async';
export * from './by';
export * from './create-code';
diff --git a/packages/shared/util-data/src/server/templates/index.ts b/packages/shared/util-data/src/server/templates/index.ts
index 193c205c..219554ce 100644
--- a/packages/shared/util-data/src/server/templates/index.ts
+++ b/packages/shared/util-data/src/server/templates/index.ts
@@ -1 +1,2 @@
+export * from './reader';
export * from './render';
diff --git a/packages/shared/util-data/src/server/templates/reader.ts b/packages/shared/util-data/src/server/templates/reader.ts
new file mode 100644
index 00000000..f00b440c
--- /dev/null
+++ b/packages/shared/util-data/src/server/templates/reader.ts
@@ -0,0 +1,7 @@
+import { createReadStream } from 'node:fs';
+import { join } from 'node:path';
+
+export function reader(file: string) {
+ const path = join(__dirname, 'assets', 'templates', file);
+ return createReadStream(path, { encoding: 'utf-8' });
+}