Skip to content

Commit bc98155

Browse files
committed
PD-5541
1 parent 492cbd3 commit bc98155

14 files changed

Lines changed: 1558 additions & 2136 deletions

angular.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@
138138
"projects/orcid-tokens/tokens.css",
139139
"src/assets/scss/orcid-ui-theme.scss",
140140
"src/assets/scss/orcid.scss",
141+
"node_modules/intl-tel-input/dist/css/intlTelInput.css",
141142
"src/styles.scss",
142143
"src/assets/scss/grid.scss"
143144
],

package.json

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,19 +39,20 @@
3939
},
4040
"private": true,
4141
"dependencies": {
42-
"@angular/animations": "^20.1.6",
43-
"@angular/cdk": "^20.1.5",
44-
"@angular/common": "^20.1.6",
45-
"@angular/compiler": "^20.3.18",
46-
"@angular/core": "^20.3.16",
42+
"@angular/animations": "20.1.6",
43+
"@angular/cdk": "20.1.6",
44+
"@angular/common": "20.1.6",
45+
"@angular/compiler": "20.1.6",
46+
"@angular/core": "20.1.6",
4747
"@angular/elements": "20.1.6",
48-
"@angular/forms": "^20.1.6",
49-
"@angular/localize": "^20.1.6",
50-
"@angular/material": "^20.1.5",
51-
"@angular/platform-browser": "^20.1.6",
52-
"@angular/platform-browser-dynamic": "^20.1.6",
53-
"@angular/router": "^20.1.6",
54-
"@angular/service-worker": "^20.1.6",
48+
"@angular/forms": "20.1.6",
49+
"@angular/localize": "20.1.6",
50+
"@angular/material": "20.1.6",
51+
"@angular/platform-browser": "20.1.6",
52+
"@angular/platform-browser-dynamic": "20.1.6",
53+
"@angular/router": "20.1.6",
54+
"@angular/service-worker": "20.1.6",
55+
"@intl-tel-input/angular": "^29.0.1",
5556
"@orcid/bibtex-parse-js": "0.0.25",
5657
"@tailwindcss/postcss": "^4.1.18",
5758
"bowser": "^2.11.0",
@@ -62,6 +63,7 @@
6263
"gulp-clean": "^0.4.0",
6364
"gulp-flatten": "^0.4.0",
6465
"helphero": "^3.6.0",
66+
"intl-tel-input": "^29.0.1",
6567
"karma-jasmine-html-reporter": "^1.7.0",
6668
"lodash": "^4.18.1",
6769
"ngx-cookie-service": "^20",
@@ -76,9 +78,9 @@
7678
"zone.js": "~0.15.1"
7779
},
7880
"devDependencies": {
79-
"@angular-devkit/build-angular": "20.1.5",
80-
"@angular/build": "^20.1.5",
81-
"@angular/cli": "^20.1.5",
81+
"@angular-devkit/build-angular": "20.1.6",
82+
"@angular/build": "20.1.6",
83+
"@angular/cli": "20.1.6",
8284
"@angular/compiler-cli": "20.1.6",
8385
"@angular/language-service": "20.1.6",
8486
"@types/jasmine": "~3.6.0",

src/app/app-routing.module.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,12 @@ const routes: Routes = [
168168
(m) => m.DeveloperToolsModule
169169
),
170170
},
171+
{
172+
path: ApplicationRoutes.smsPoc,
173+
canActivateChild: [AuthenticatedGuard],
174+
loadChildren: () =>
175+
import('./sms-poc/sms-poc.module').then((m) => m.SmsPocModule),
176+
},
171177
{
172178
path: '404',
173179
loadChildren: () =>

src/app/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ export const ApplicationRoutes = {
111111
resetPasswordEmail: 'reset-password-email',
112112
selfService: 'self-service',
113113
developerTools: 'developer-tools',
114+
smsPoc: 'sms-poc',
114115
home: '',
115116
}
116117

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

141143
export const ApplicationDynamicRoutesLabels = {
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Injectable } from '@angular/core'
2+
import { HttpClient } from '@angular/common/http'
3+
import { catchError } from 'rxjs/operators'
4+
import { ErrorHandlerService } from '../error-handler/error-handler.service'
5+
import { SmsPocRequest, SmsPocResponse } from 'src/app/types/sms-poc.endpoint'
6+
7+
@Injectable({
8+
providedIn: 'root',
9+
})
10+
export class SmsPocService {
11+
constructor(
12+
private _http: HttpClient,
13+
private _errorHandler: ErrorHandlerService
14+
) {}
15+
16+
send(data: SmsPocRequest) {
17+
return this._http
18+
.post<SmsPocResponse>(
19+
runtimeEnvironment.API_WEB + 'sms-poc/send.json',
20+
data,
21+
{
22+
withCredentials: true,
23+
}
24+
)
25+
.pipe(catchError((error) => this._errorHandler.handleError(error)))
26+
}
27+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<mat-card class="sms-poc-card max-w-[560px] sm:p-10! p-6!">
2+
<h1 class="orc-font-heading-small font-normal mb-4" i18n="@@smsPoc.title">
3+
SMS POC
4+
</h1>
5+
<p class="sms-poc-description orc-font-body-small mb-4" i18n="@@smsPoc.description">
6+
Send a test SMS. Choose AWS or Twilio for each send.
7+
</p>
8+
9+
<app-alert-message type="warning" role="note" class="mb-6 block">
10+
<div content>
11+
<span
12+
i18n="@@smsPoc.sandboxNotice"
13+
>
14+
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.
15+
</span>
16+
</div>
17+
</app-alert-message>
18+
19+
<form [formGroup]="smsForm" (ngSubmit)="onSubmit()" class="grid gap-4">
20+
<div class="provider-selection">
21+
<span
22+
class="orc-font-small-print leading-4.5 font-bold! block mb-2"
23+
i18n="@@smsPoc.provider"
24+
>
25+
Provider
26+
</span>
27+
<mat-radio-group
28+
formControlName="provider"
29+
color="primary"
30+
class="provider-group flex gap-4"
31+
>
32+
<mat-radio-button value="aws" i18n="@@smsPoc.providerAws">
33+
AWS SNS
34+
</mat-radio-button>
35+
<mat-radio-button value="twilio" i18n="@@smsPoc.providerTwilio">
36+
Twilio
37+
</mat-radio-button>
38+
</mat-radio-group>
39+
</div>
40+
41+
<div class="phone-field">
42+
<label
43+
class="orc-font-small-print leading-4.5 font-bold! block mb-2"
44+
for="sms-poc-phone"
45+
i18n="@@smsPoc.phoneNumber"
46+
>
47+
Phone number
48+
</label>
49+
<div class="phone-input-shell">
50+
<intl-tel-input
51+
formControlName="phoneNumber"
52+
[inputAttributes]="{ id: 'sms-poc-phone' }"
53+
[initialCountry]="'cr'"
54+
[loadUtils]="loadUtils"
55+
/>
56+
</div>
57+
@if (invalidPhoneMessage) {
58+
<mat-error class="orc-font-small-print mt-2 block">
59+
{{ invalidPhoneMessage }}
60+
</mat-error>
61+
}
62+
</div>
63+
64+
<mat-form-field appearance="outline" [hideRequiredMarker]="true">
65+
<mat-label i18n="@@smsPoc.message">Message</mat-label>
66+
<textarea
67+
matInput
68+
formControlName="message"
69+
rows="4"
70+
maxlength="1600"
71+
></textarea>
72+
<mat-hint align="end">{{ message?.value?.length || 0 }}/1600</mat-hint>
73+
@if (message?.hasError('required') && message?.touched) {
74+
<mat-error i18n="@@smsPoc.messageRequired">Message is required</mat-error>
75+
} @if (message?.hasError('maxlength') && message?.touched) {
76+
<mat-error i18n="@@smsPoc.messageTooLong">
77+
Message must be 1600 characters or fewer
78+
</mat-error>
79+
}
80+
</mat-form-field>
81+
82+
@if (backendError) {
83+
<app-alert-message type="warning" role="alert" class="mb-2">
84+
<div content>{{ backendError }}</div>
85+
</app-alert-message>
86+
} @if (response?.success) {
87+
<app-alert-message type="success" role="status" class="mb-2">
88+
<div content>
89+
<span i18n="@@smsPoc.sent">SMS sent.</span>
90+
<span>
91+
{{ response.provider }}:
92+
{{ response.providerMessageId || response.status }}
93+
</span>
94+
</div>
95+
</app-alert-message>
96+
} @if (loading) {
97+
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
98+
}
99+
100+
<button
101+
mat-raised-button
102+
color="primary"
103+
type="submit"
104+
[disabled]="loading"
105+
[ngClass]="{ 'button-loading': loading }"
106+
i18n="@@smsPoc.send"
107+
>
108+
Send SMS
109+
</button>
110+
</form>
111+
</mat-card>
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
:host {
2+
display: flex;
3+
align-items: center;
4+
justify-content: center;
5+
min-height: 100vh;
6+
width: 100%;
7+
padding: 1rem;
8+
box-sizing: border-box;
9+
background:
10+
radial-gradient(circle at 0% 0%, rgba(6, 136, 219, 0.08), transparent 38%),
11+
radial-gradient(circle at 100% 100%, rgba(255, 183, 77, 0.1), transparent 36%);
12+
}
13+
14+
.sms-poc-card {
15+
border: 1px solid rgba(0, 0, 0, 0.08);
16+
border-radius: 14px;
17+
box-shadow: 0 12px 28px -20px rgba(0, 0, 0, 0.45);
18+
}
19+
20+
.sms-poc-description {
21+
color: #334155;
22+
}
23+
24+
.provider-selection {
25+
padding: 0.25rem 0.5rem 0.75rem;
26+
border-radius: 10px;
27+
border: 1px dashed rgba(6, 136, 219, 0.28);
28+
background: linear-gradient(180deg, rgba(6, 136, 219, 0.06), rgba(6, 136, 219, 0.02));
29+
}
30+
31+
.provider-group {
32+
flex-wrap: wrap;
33+
}
34+
35+
.phone-input-shell {
36+
padding: 0.35rem;
37+
border-radius: 12px;
38+
border: 1px solid rgba(15, 23, 42, 0.12);
39+
background: #fff;
40+
transition: border-color 160ms ease, box-shadow 160ms ease;
41+
}
42+
43+
.phone-input-shell:focus-within {
44+
border-color: #0688db;
45+
box-shadow: 0 0 0 3px rgba(6, 136, 219, 0.18);
46+
}
47+
48+
intl-tel-input {
49+
display: block;
50+
}
51+
52+
:host ::ng-deep intl-tel-input .iti {
53+
width: 100%;
54+
}
55+
56+
:host ::ng-deep intl-tel-input .iti__selected-country-primary {
57+
padding: 0.5rem 0.45rem;
58+
border-radius: 8px;
59+
}
60+
61+
:host ::ng-deep intl-tel-input input[type='tel'] {
62+
width: 100%;
63+
min-height: 3rem;
64+
border: 0;
65+
border-radius: 9px;
66+
background: #fff;
67+
font-size: 0.95rem;
68+
padding: 0.65rem 0.75rem;
69+
color: #0f172a;
70+
outline: none;
71+
}
72+
73+
:host ::ng-deep .provider-group .mat-mdc-radio-button {
74+
background: #ffffff;
75+
border: 1px solid rgba(15, 23, 42, 0.12);
76+
border-radius: 999px;
77+
padding: 0.35rem 0.75rem;
78+
}
79+
80+
@media (max-width: 640px) {
81+
:host {
82+
align-items: flex-start;
83+
min-height: auto;
84+
padding: 0.75rem;
85+
}
86+
87+
.provider-group {
88+
gap: 0.5rem;
89+
}
90+
}

0 commit comments

Comments
 (0)