Skip to content

Commit

Permalink
portalicious: custom message popup
Browse files Browse the repository at this point in the history
  • Loading branch information
aberonni committed Oct 23, 2024
1 parent 5ebf503 commit 180deca
Show file tree
Hide file tree
Showing 15 changed files with 733 additions and 13 deletions.
1 change: 1 addition & 0 deletions interfaces/Portalicious/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ module.exports = tseslint.config(
'p-button[iconPos]',
'p-columnFilter[display]',
'p-contextMenu[appendTo]',
'p-dropdown[appendTo]',
'p-iconField[iconPosition]',
'p-sidebar[position]',
'p-table[stateKey]',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Injectable, Signal } from '@angular/core';

import { DomainApiService } from '~/domains/domain-api.service';
import { MessageTemplate } from '~/domains/notification/notification.helper';

const BASE_ENDPOINT = (projectId: Signal<number>) => [
'notifications',
projectId,
];

@Injectable({
providedIn: 'root',
})
export class NotificationApiService extends DomainApiService {
getMessageTemplates(projectId: Signal<number | undefined>) {
return this.generateQueryOptions<MessageTemplate[]>({
path: [
...BASE_ENDPOINT(projectId as Signal<number>),
'message-templates',
],
enabled: () => !!projectId(),
});
}

public invalidateMessageTemplates(projectId: Signal<number>): Promise<void> {
const path = [...BASE_ENDPOINT(projectId), 'message-templates'];

return this.queryClient.invalidateQueries({
queryKey: this.pathToQueryKey(path),
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { MessageTemplateController } from '@121-service/src/notifications/message-template/message-template.controller';

import { Dto121Service } from '~/utils/dto-type';
import { ArrayElement } from '~/utils/type-helpers';

export type MessageTemplate = ArrayElement<
Dto121Service<MessageTemplateController['getMessageTemplatesByProgramId']>
>;
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { inject, Injectable, Signal } from '@angular/core';

import { queryOptions } from '@tanstack/angular-query-experimental';

import { SendCustomTextDto } from '@121-service/src/registration/dto/send-custom-text.dto';

import { DomainApiService } from '~/domains/domain-api.service';
import {
ActitivitiesResponse,
Expand All @@ -25,6 +27,10 @@ const BASE_ENDPOINT = (projectId: Signal<number>) => [
'registrations',
];

export type SendMessageData =
| { customMessage: string }
| { messageTemplateKey: string };

@Injectable({
providedIn: 'root',
})
Expand Down Expand Up @@ -66,6 +72,41 @@ export class RegistrationApiService extends DomainApiService {
});
}

sendMessage({
projectId,
paginateQuery,
messageData,
}: {
projectId: Signal<number>;
paginateQuery: PaginateQuery | undefined;
messageData: SendMessageData;
}) {
let body: Partial<SendCustomTextDto>;

if ('customMessage' in messageData) {
body = {
message: messageData.customMessage,
skipMessageValidation: false,
};
} else {
body = {
messageTemplateKey: messageData.messageTemplateKey,
skipMessageValidation: false,
};
}

return this.httpWrapperService.perform121ServiceRequest({
method: 'POST',
endpoint: this.pathToQueryKey([
...BASE_ENDPOINT(projectId),
'message',
]).join('/'),
body,
params:
this.paginateQueryService.paginateQueryToHttpParams(paginateQuery),
});
}

getActivityLog(projectId: Signal<number>, registrationId: Signal<number>) {
return this.generateQueryOptions<ActitivitiesResponse>({
path: [...BASE_ENDPOINT(projectId), registrationId, 'activities'],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<div class="space-y-2">
<p i18n>
Custom messages are <b>not automatically translated</b> to the
registration's preferred language.
</p>

<p>
<strong i18n> Message: </strong>
<app-info-tooltip
message="Note that if you go over 160 characters the Person affected may receive the message as multiple texts, if they have a feature-(non-smart-)phone."
i18n-message
/>
</p>

<p i18n>
Use &commat; to add personalized fields.
<!-- TODO: AB#30847 figure out correct URL -->
<a href="https://manual.121.global/">Learn more</a>
</p>

<div>
<!-- TODO: AB#30847 add possibility to insert placeholders from textarea -->
<textarea
rows="5"
pInputTextarea
[(ngModel)]="customMessageInternalModel"
[disabled]="customMessageDisabled()"
autocomplete="off"
class="w-full"
[ngClass]="{
'ng-invalid ng-dirty': !!error(),
}"
></textarea>

@if (error()) {
<app-form-error [error]="error()" />
} @else {
<p
i18n
class="w-full text-end txt-body-s"
>
(>20) characters.
</p>
}
</div>

<p i18n>
*Please make sure registration data is available for all used personalized
fields.
</p>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { NgClass } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
input,
model,
} from '@angular/core';
import {
ControlValueAccessor,
FormsModule,
NG_VALUE_ACCESSOR,
} from '@angular/forms';

import { InputTextareaModule } from 'primeng/inputtextarea';

import { FormErrorComponent } from '~/components/form-error/form-error.component';
import { InfoTooltipComponent } from '~/components/info-tooltip/info-tooltip.component';

@Component({
selector: 'app-custom-message-control',
standalone: true,
imports: [
FormsModule,
InfoTooltipComponent,
InputTextareaModule,
NgClass,
FormErrorComponent,
],
templateUrl: './custom-message-control.component.html',
styles: ``,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: CustomMessageControlComponent,
multi: true,
},
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomMessageControlComponent implements ControlValueAccessor {
readonly error = input<string>();

customMessageInternalModel = model<string>('');
customMessageDisabled = model<boolean>(false);

writeValue(value: string) {
this.customMessageInternalModel.set(value);
}

registerOnChange(fn: (value: string) => void) {
this.customMessageInternalModel.subscribe(fn);
}

// eslint-disable-next-line @typescript-eslint/no-empty-function
registerOnTouched() {}

setDisabledState(setDisabledState: boolean) {
this.customMessageDisabled.set(setDisabledState);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<p
i18n
class="mb-2 font-bold"
>
Message preview
</p>
<textarea
rows="5"
pInputTextarea
[value]="messageText()"
[disabled]="true"
class="w-full"
></textarea>
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {
ChangeDetectionStrategy,
Component,
computed,
input,
} from '@angular/core';
import { FormsModule } from '@angular/forms';

import { InputTextareaModule } from 'primeng/inputtextarea';

import { SendMessageData } from '~/domains/registration/registration.api.service';

@Component({
selector: 'app-custom-message-preview',
standalone: true,
imports: [InputTextareaModule, FormsModule],
templateUrl: './custom-message-preview.component.html',
styles: ``,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomMessagePreviewComponent {
sendMessageData = input<SendMessageData>();
// TODO: AB#30847 add additional property for dummy data to be used in the preview

messageText = computed(() => {
const data = this.sendMessageData();

if (data === undefined) {
return '';
}

// TODO: AB#30847 replace placeholders in message with dummy data
if ('customMessage' in data) {
return data.customMessage;
}

return data.messageTemplateKey;
});
}
Loading

0 comments on commit 180deca

Please sign in to comment.