Skip to content
Merged

PD-5541 #2845

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@
"projects/orcid-tokens/tokens.css",
"src/assets/scss/orcid-ui-theme.scss",
"src/assets/scss/orcid.scss",
"node_modules/intl-tel-input/dist/css/intlTelInput.css",
"src/styles.scss",
"src/assets/scss/grid.scss"
],
Expand Down
32 changes: 17 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,20 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^20.1.6",
"@angular/cdk": "^20.1.5",
"@angular/common": "^20.1.6",
"@angular/compiler": "^20.3.18",
"@angular/core": "^20.3.16",
"@angular/animations": "20.1.6",
"@angular/cdk": "20.1.6",
"@angular/common": "20.1.6",
"@angular/compiler": "20.1.6",
"@angular/core": "20.1.6",
"@angular/elements": "20.1.6",
"@angular/forms": "^20.1.6",
"@angular/localize": "^20.1.6",
"@angular/material": "^20.1.5",
"@angular/platform-browser": "^20.1.6",
"@angular/platform-browser-dynamic": "^20.1.6",
"@angular/router": "^20.1.6",
"@angular/service-worker": "^20.1.6",
"@angular/forms": "20.1.6",
"@angular/localize": "20.1.6",
"@angular/material": "20.1.6",
"@angular/platform-browser": "20.1.6",
"@angular/platform-browser-dynamic": "20.1.6",
"@angular/router": "20.1.6",
"@angular/service-worker": "20.1.6",
"@intl-tel-input/angular": "^29.0.1",
"@orcid/bibtex-parse-js": "0.0.25",
"@tailwindcss/postcss": "^4.1.18",
"bowser": "^2.11.0",
Expand All @@ -62,6 +63,7 @@
"gulp-clean": "^0.4.0",
"gulp-flatten": "^0.4.0",
"helphero": "^3.6.0",
"intl-tel-input": "^29.0.1",
"karma-jasmine-html-reporter": "^1.7.0",
"lodash": "^4.18.1",
"ngx-cookie-service": "^20",
Expand All @@ -76,9 +78,9 @@
"zone.js": "~0.15.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "20.1.5",
"@angular/build": "^20.1.5",
"@angular/cli": "^20.1.5",
"@angular-devkit/build-angular": "20.1.6",
"@angular/build": "20.1.6",
"@angular/cli": "20.1.6",
"@angular/compiler-cli": "20.1.6",
"@angular/language-service": "20.1.6",
"@types/jasmine": "~3.6.0",
Expand Down
6 changes: 6 additions & 0 deletions src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,12 @@ const routes: Routes = [
(m) => m.DeveloperToolsModule
),
},
{
path: ApplicationRoutes.smsPoc,
canActivateChild: [AuthenticatedGuard],
loadChildren: () =>
import('./sms-poc/sms-poc.module').then((m) => m.SmsPocModule),
},
{
path: '404',
loadChildren: () =>
Expand Down
2 changes: 2 additions & 0 deletions src/app/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ export const ApplicationRoutes = {
resetPasswordEmail: 'reset-password-email',
selfService: 'self-service',
developerTools: 'developer-tools',
smsPoc: 'sms-poc',
home: '',
}

Expand All @@ -136,6 +137,7 @@ export const ApplicationRoutesLabels = {
[ApplicationRoutes.resetPasswordEmail]: $localize`:@@share.resetPasswordEmail:Reset password - ORCID`,
[ApplicationRoutes.selfService]: $localize`:@@share.selfService:Self Service - ORCID`,
[ApplicationRoutes.developerTools]: $localize`:@@share.developerTools:Developer tools - ORCID`,
[ApplicationRoutes.smsPoc]: $localize`:@@share.smsPoc:SMS POC - ORCID`,
}

export const ApplicationDynamicRoutesLabels = {
Expand Down
27 changes: 27 additions & 0 deletions src/app/core/sms-poc/sms-poc.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Injectable } from '@angular/core'
import { HttpClient } from '@angular/common/http'
import { catchError } from 'rxjs/operators'
import { ErrorHandlerService } from '../error-handler/error-handler.service'
import { SmsPocRequest, SmsPocResponse } from 'src/app/types/sms-poc.endpoint'

@Injectable({
providedIn: 'root',
})
export class SmsPocService {
constructor(
private _http: HttpClient,
private _errorHandler: ErrorHandlerService
) {}

send(data: SmsPocRequest) {
return this._http
.post<SmsPocResponse>(
runtimeEnvironment.API_WEB + 'sms-poc/send.json',
data,
{
withCredentials: true,
}
)
.pipe(catchError((error) => this._errorHandler.handleError(error)))
}
}
111 changes: 111 additions & 0 deletions src/app/sms-poc/pages/sms-poc/sms-poc.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<mat-card class="sms-poc-card max-w-[560px] sm:p-10! p-6!">
<h1 class="orc-font-heading-small font-normal mb-4" i18n="@@smsPoc.title">
SMS POC
</h1>
<p class="sms-poc-description orc-font-body-small mb-4" i18n="@@smsPoc.description">
Send a test SMS. Choose AWS or Twilio for each send.
</p>

<app-alert-message type="warning" role="note" class="mb-6 block">
<div content>
<span
i18n="@@smsPoc.sandboxNotice"
>
This POC currently runs in SANDBOX mode. If you want to test actual SMS delivery to your phone, please reach out to Leo on Slack to add your phone number to our AWS SNS ORCID Friend Sandbox and the Twilio accounts.
</span>
</div>
</app-alert-message>

<form [formGroup]="smsForm" (ngSubmit)="onSubmit()" class="grid gap-4">
<div class="provider-selection">
<span
class="orc-font-small-print leading-4.5 font-bold! block mb-2"
i18n="@@smsPoc.provider"
>
Provider
</span>
<mat-radio-group
formControlName="provider"
color="primary"
class="provider-group flex gap-4"
>
<mat-radio-button value="aws" i18n="@@smsPoc.providerAws">
AWS SNS
</mat-radio-button>
<mat-radio-button value="twilio" i18n="@@smsPoc.providerTwilio">
Twilio
</mat-radio-button>
</mat-radio-group>
</div>

<div class="phone-field">
<label
class="orc-font-small-print leading-4.5 font-bold! block mb-2"
for="sms-poc-phone"
i18n="@@smsPoc.phoneNumber"
>
Phone number
</label>
<div class="phone-input-shell">
<intl-tel-input
formControlName="phoneNumber"
[inputAttributes]="{ id: 'sms-poc-phone' }"
[initialCountry]="'cr'"
[loadUtils]="loadUtils"
/>
</div>
@if (invalidPhoneMessage) {
<mat-error class="orc-font-small-print mt-2 block">
{{ invalidPhoneMessage }}
</mat-error>
}
</div>

<mat-form-field appearance="outline" [hideRequiredMarker]="true">
<mat-label i18n="@@smsPoc.message">Message</mat-label>
<textarea
matInput
formControlName="message"
rows="4"
maxlength="1600"
></textarea>
<mat-hint align="end">{{ message?.value?.length || 0 }}/1600</mat-hint>
@if (message?.hasError('required') && message?.touched) {
<mat-error i18n="@@smsPoc.messageRequired">Message is required</mat-error>
} @if (message?.hasError('maxlength') && message?.touched) {
<mat-error i18n="@@smsPoc.messageTooLong">
Message must be 1600 characters or fewer
</mat-error>
}
</mat-form-field>

@if (backendError) {
<app-alert-message type="warning" role="alert" class="mb-2">
<div content>{{ backendError }}</div>
</app-alert-message>
} @if (response?.success) {
<app-alert-message type="success" role="status" class="mb-2">
<div content>
<span i18n="@@smsPoc.sent">SMS sent.</span>
<span>
{{ response.provider }}:
{{ response.providerMessageId || response.status }}
</span>
</div>
</app-alert-message>
} @if (loading) {
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
}

<button
mat-raised-button
color="primary"
type="submit"
[disabled]="loading"
[ngClass]="{ 'button-loading': loading }"
i18n="@@smsPoc.send"
>
Send SMS
</button>
</form>
</mat-card>
90 changes: 90 additions & 0 deletions src/app/sms-poc/pages/sms-poc/sms-poc.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
:host {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
width: 100%;
padding: 1rem;
box-sizing: border-box;
background:
radial-gradient(circle at 0% 0%, rgba(6, 136, 219, 0.08), transparent 38%),
radial-gradient(circle at 100% 100%, rgba(255, 183, 77, 0.1), transparent 36%);
}

.sms-poc-card {
border: 1px solid rgba(0, 0, 0, 0.08);
border-radius: 14px;
box-shadow: 0 12px 28px -20px rgba(0, 0, 0, 0.45);
}

.sms-poc-description {
color: #334155;
}

.provider-selection {
padding: 0.25rem 0.5rem 0.75rem;
border-radius: 10px;
border: 1px dashed rgba(6, 136, 219, 0.28);
background: linear-gradient(180deg, rgba(6, 136, 219, 0.06), rgba(6, 136, 219, 0.02));
}

.provider-group {
flex-wrap: wrap;
}

.phone-input-shell {
padding: 0.35rem;
border-radius: 12px;
border: 1px solid rgba(15, 23, 42, 0.12);
background: #fff;
transition: border-color 160ms ease, box-shadow 160ms ease;
}

.phone-input-shell:focus-within {
border-color: #0688db;
box-shadow: 0 0 0 3px rgba(6, 136, 219, 0.18);
}

intl-tel-input {
display: block;
}

:host ::ng-deep intl-tel-input .iti {
width: 100%;
}

:host ::ng-deep intl-tel-input .iti__selected-country-primary {
padding: 0.5rem 0.45rem;
border-radius: 8px;
}

:host ::ng-deep intl-tel-input input[type='tel'] {
width: 100%;
min-height: 3rem;
border: 0;
border-radius: 9px;
background: #fff;
font-size: 0.95rem;
padding: 0.65rem 0.75rem;
color: #0f172a;
outline: none;
}

:host ::ng-deep .provider-group .mat-mdc-radio-button {
background: #ffffff;
border: 1px solid rgba(15, 23, 42, 0.12);
border-radius: 999px;
padding: 0.35rem 0.75rem;
}

@media (max-width: 640px) {
:host {
align-items: flex-start;
min-height: auto;
padding: 0.75rem;
}

.provider-group {
gap: 0.5rem;
}
}
Loading
Loading