Skip to content

Commit 879f3f6

Browse files
committed
added connected fields to submit claim and buy new insurance scenarios
1 parent 518de53 commit 879f3f6

File tree

12 files changed

+877
-223
lines changed

12 files changed

+877
-223
lines changed

client/public/locales/en/Common.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,9 @@
66
"Copyright": "© 2025 Docusign Inc.",
77
"LogOutLink": "Log out",
88
"LogInButton": "Log in",
9-
"AlertMessage": "Sorry, there was some issue with the Code Grant Authorization, so you have been logged with JWT authorization"
10-
}
9+
"DownloadExtensionsButton": "Download Extensions and try again",
10+
"DownloadExtensionsHeader": "Extensions Missing",
11+
"ContinueWithoutExtensionsButton": "Continue Without Extensions",
12+
"AlertMessage": "Sorry, there was some issue with the Code Grant Authorization, so you have been logged with JWT authorization",
13+
"DownloadExtensionsMessage": "Extension apps not installed for this account. Either go to <a href=\"https://apps-d.docusign.com/app-center\" target=\"_blank\" rel=\"noopener noreferrer\">App Center</a> to install them and try again, or you can continue to use this without the Data Validation apps.<br><br><strong>Links to extension apps:</strong><br> - <a href=\"https://apps-d.docusign.com/app-center/app/6ff9ae39-ad45-4d04-b0c2-a6e2214f5925\" target=\"_blank\" rel=\"noopener noreferrer\">Twilio</a> or <a href=\"https://apps-d.docusign.com/app-center/app/5e3b623f-afaf-45da-b6a0-f5abc3c32128\" target=\"_blank\" rel=\"noopener noreferrer\">Emailable</a> (for email verification)<br> - <a href=\"https://apps-d.docusign.com/app-center/app/04bfc1ae-1ba0-42d0-8c02-264417a7b234\" target=\"_blank\" rel=\"noopener noreferrer\">Smarty</a> (for address verification)"
14+
}

client/src/api/insuranceAPI.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,20 @@ export async function submitClaim(request) {
3131
}
3232
}
3333

34+
export async function getExtensions() {
35+
try {
36+
const response = await axios.get(
37+
process.env.REACT_APP_API_BASE_URL + "/extensionApps",
38+
{
39+
withCredentials: true
40+
}
41+
);
42+
return handleResponse(response);
43+
} catch (error) {
44+
handleError(error);
45+
}
46+
}
47+
3448
export async function getStatus(fromDate) {
3549
try {
3650
const response = await axios.get(
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import React from "react";
2+
import { useTranslation } from "react-i18next";
3+
import { Modal, Button, Container } from "react-bootstrap";
4+
5+
export const ExtensionsModal = ({ show, onDownloadExtensions, onHide, title, message }) => {
6+
const { t } = useTranslation("Common");
7+
8+
const handleContinue = () => {
9+
onHide();
10+
};
11+
12+
return (
13+
<Modal show={show} onHide={onHide} centered>
14+
<Modal.Header closeButton>
15+
<Modal.Title>{title}</Modal.Title>
16+
</Modal.Header>
17+
18+
<Modal.Body>
19+
<Container>
20+
<p dangerouslySetInnerHTML={{ __html: message }} />
21+
</Container>
22+
</Modal.Body>
23+
24+
<Modal.Footer style={{ flexWrap: "inherit"}}>
25+
<Button className="btn btn-success" onClick={onDownloadExtensions} style={{ color: "white" }}>
26+
{t("DownloadExtensionsButton")}
27+
</Button>
28+
<Button className="btn btn-secondary" onClick={handleContinue}>
29+
{t("ContinueWithoutExtensionsButton")}
30+
</Button>
31+
</Modal.Footer>
32+
</Modal>
33+
);
34+
};

client/src/pages/buyNewInsurance/index.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { SEND_REQEUST_SUCCESS } from "./actionTypes";
88
import { useTranslation } from "react-i18next";
99
import LoggedUserContext from "../../contexts/logged-user/logged-user.context";
1010
import { checkUnlogged } from "../../api/auth";
11+
import { ExtensionsModal } from "../../components/ExtensionsModal.js";
1112

1213
const initialState = {
1314
errors: [],
@@ -27,11 +28,14 @@ const initialState = {
2728
};
2829

2930
export const BuyNewInsurance = () => {
31+
const { t: tCommon } = useTranslation("Common");
3032
const { t } = useTranslation("BuyNewInsurance");
3133
const states = t("States", { returnObjects: true });
3234
const options = t("Options", { returnObjects: true });
3335
const [option, setOption] = useState(options[0]);
3436
const [state, dispatch] = useReducer(reducer, initialState);
37+
const [areExtensionsPresent, setAreExtensionsPresent] = useState(false);
38+
const [modalShow, setModalShow] = useState(false);
3539
const [request, setRequestData] = useState({ ...initialState.request });
3640
const [requesting, setRequesting] = useState(false);
3741
const [errors, setErrors] = useState({});
@@ -46,6 +50,8 @@ export const BuyNewInsurance = () => {
4650
if (!formIsValid()) {
4751
return;
4852
}
53+
54+
const useWithoutExtension = sessionStorage.getItem("useWithoutExtensions") === "true";
4955
const body = {
5056
"callback-url": process.env.REACT_APP_DS_RETURN_URL + "/signing_complete",
5157
user: {
@@ -57,6 +63,7 @@ export const BuyNewInsurance = () => {
5763
state: request.state,
5864
zip_code: request.zipCode
5965
},
66+
useWithoutExtension: useWithoutExtension,
6067
insurance: {
6168
detail1: {
6269
name: option.detail1,
@@ -71,6 +78,15 @@ export const BuyNewInsurance = () => {
7178
setRequesting(true);
7279

7380
try {
81+
if (!useWithoutExtension) {
82+
const extensions = await api.getExtensions();
83+
if(extensions.areExtensionsPresent === false){
84+
setAreExtensionsPresent(true);
85+
setModalShow(true);
86+
return;
87+
}
88+
}
89+
7490
const savedRequest = await api.buyNewInsurance(body, setShowJWTModal);
7591
dispatch({
7692
type: SEND_REQEUST_SUCCESS,
@@ -179,6 +195,25 @@ export const BuyNewInsurance = () => {
179195
/>
180196
<ApiDescription />
181197
</div>
198+
199+
{areExtensionsPresent && (
200+
<ExtensionsModal
201+
show={modalShow}
202+
onDownloadExtensions={
203+
() => {
204+
setModalShow(false);
205+
}
206+
}
207+
onHide={
208+
() => {
209+
sessionStorage.setItem("useWithoutExtensions", "true");
210+
setModalShow(false);
211+
}
212+
}
213+
title={tCommon("DownloadExtensionsHeader")}
214+
message= {tCommon("DownloadExtensionsMessage")}
215+
/>
216+
)}
182217
</section>
183218
);
184219
} else {

client/src/pages/submitClaim/index.js

Lines changed: 62 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as insuranceAPI from "../../api/insuranceAPI";
88
import { useTranslation } from "react-i18next";
99
import LoggedUserContext from "../../contexts/logged-user/logged-user.context";
1010
import { checkUnlogged } from "../../api/auth";
11+
import { ExtensionsModal } from "../../components/ExtensionsModal.js";
1112

1213
const initialState = {
1314
errors: [],
@@ -25,11 +26,14 @@ const initialState = {
2526
};
2627

2728
export const SubmitClaim = () => {
29+
const { t: tCommon } = useTranslation("Common");
2830
const { t } = useTranslation("SubmitClaim");
2931
const states = t("States", { returnObjects: true });
3032
const options = t("Options", { returnObjects: true });
3133
const [option, setOption] = useState(options[0]);
3234
const [state, dispatch] = useReducer(reducer, initialState);
35+
const [areExtensionsPresent, setAreExtensionsPresent] = useState(false);
36+
const [modalShow, setModalShow] = useState(false);
3337
const [request, setRequestData] = useState({ ...initialState.request });
3438
const [requesting, setRequesting] = useState(false);
3539
const [errors, setErrors] = useState({});
@@ -46,6 +50,7 @@ export const SubmitClaim = () => {
4650
return;
4751
}
4852

53+
const useWithoutExtension = sessionStorage.getItem("useWithoutExtensions") === "true";
4954
const body = {
5055
"callback-url": process.env.REACT_APP_DS_RETURN_URL + "/signing_complete",
5156
claim: {
@@ -58,17 +63,28 @@ export const SubmitClaim = () => {
5863
zip_code: request.zipCode,
5964
type: option.name,
6065
timestamp: date.toGMTString(),
61-
description: request.description
66+
description: request.description,
67+
useWithoutExtension: useWithoutExtension
6268
}
6369
};
6470
setRequesting(true);
6571
try {
72+
sessionStorage.setItem("useWithoutExtensions", "false");
73+
if (!useWithoutExtension) {
74+
const extensions = await insuranceAPI.getExtensions();
75+
if(extensions.areExtensionsPresent === false){
76+
setAreExtensionsPresent(true);
77+
setModalShow(true);
78+
return;
79+
}
80+
}
81+
6682
const savedRequest = await insuranceAPI.submitClaim(body);
6783
dispatch({
6884
type: SEND_REQEUST_SUCCESS,
6985
payload: {
7086
envelopeId: savedRequest.envelope_id,
71-
redirectUrl: savedRequest.redirect_url
87+
redirectUrl: savedRequest.redirect_url,
7288
}
7389
});
7490
} catch (error) {
@@ -139,29 +155,49 @@ export const SubmitClaim = () => {
139155
setErrors(errors);
140156
return Object.keys(errors).length === 0;
141157
}
142-
158+
143159
if (!state.redirectUrl) {
144-
return (
145-
<section className="container content-section">
146-
<div className="row">
147-
<RequestForm
148-
request={request}
149-
states={states}
150-
options={options}
151-
requesting={requesting}
152-
onChange={handleChange}
153-
onSelect={handleSelect}
154-
onSave={handleSave}
155-
errors={errors}
156-
setDate={setDate}
157-
setOption={setOption}
158-
date={date}
159-
/>
160-
<ApiDescription />
161-
</div>
162-
</section>
163-
);
164-
} else {
165-
return <Frame src={state.redirectUrl} />;
166-
}
160+
return (
161+
<section className="container content-section">
162+
<div className="row">
163+
<RequestForm
164+
request={request}
165+
states={states}
166+
options={options}
167+
requesting={requesting}
168+
onChange={handleChange}
169+
onSelect={handleSelect}
170+
onSave={handleSave}
171+
errors={errors}
172+
setDate={setDate}
173+
setOption={setOption}
174+
date={date}
175+
/>
176+
<ApiDescription />
177+
</div>
178+
179+
{areExtensionsPresent && (
180+
<ExtensionsModal
181+
show={modalShow}
182+
onDownloadExtensions={
183+
() => {
184+
setModalShow(false);
185+
}
186+
}
187+
onHide={
188+
() => {
189+
sessionStorage.setItem("useWithoutExtensions", "true");
190+
setModalShow(false);
191+
}
192+
}
193+
title={tCommon("DownloadExtensionsHeader")}
194+
message= {tCommon("DownloadExtensionsMessage")}
195+
/>
196+
)}
197+
</section>
198+
);
199+
} else {
200+
return <Frame src={state.redirectUrl} />;
201+
}
202+
167203
};

server/app/api/requests.py

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,40 @@
66
from app.api.utils import process_error, check_token
77
from app.document import DsDocument
88
from app.envelope import Envelope
9+
from app.extensions import Extensions
910

1011
from .session_data import SessionData
12+
from app.ds_config import CONNECTED_FIELDS_BASE_HOST
13+
import json
1114

1215

1316
requests = Blueprint('requests', __name__)
1417

18+
@requests.route('/extensionApps', methods=['GET'])
19+
@cross_origin()
20+
def extension_apps():
21+
"""Request for extension apps"""
22+
23+
access_token = session.get('access_token')
24+
account_id = session.get('account_id')
25+
26+
try:
27+
extensions = Extensions.getExtensions(account_id, access_token, CONNECTED_FIELDS_BASE_HOST)
28+
29+
session['extensions'] = json.dumps(extensions)
30+
actual_app_ids = [item["appId"] for item in extensions]
31+
32+
address_extension_id = Extensions.getAddressExtensionId()
33+
email_extension_ids = Extensions.getEmailExtensionIds()
34+
35+
has_required_app = address_extension_id in actual_app_ids
36+
has_at_least_one_optional = any(app_id in actual_app_ids for app_id in email_extension_ids)
37+
38+
has_all_app_ids = has_required_app and has_at_least_one_optional
39+
except ApiException as exc:
40+
return process_error(exc)
41+
return jsonify({'areExtensionsPresent': has_all_app_ids})
42+
1543

1644
@requests.route('/requests/claim', methods=['POST'])
1745
@cross_origin()
@@ -24,14 +52,20 @@ def submit_claim():
2452
return jsonify(message='Invalid JSON input'), 400
2553

2654
claim = req_json['claim']
55+
useWithoutExtension = claim['useWithoutExtension']
56+
2757
envelope_args = {
2858
'signer_client_id': 1000,
2959
'ds_return_url': req_json['callback-url'],
3060
}
3161

3262
try:
3363
# Create envelope
34-
envelope = DsDocument.create_claim('submit-claim.html', claim, envelope_args)
64+
if useWithoutExtension == True:
65+
envelope = DsDocument.create_claim_without_extension('submit-claim.html', claim, envelope_args)
66+
else:
67+
extensions = json.loads(session.get('extensions'))
68+
envelope = DsDocument.create_claim('submit-claim.html', claim, envelope_args, extensions)
3569
# Submit envelope to the Docusign
3670
envelope_id = Envelope.send(envelope, session)
3771
except ApiException as exc:
@@ -58,21 +92,31 @@ def buy_new_insurance():
5892
return jsonify(message='Invalid JSON input'), 400
5993

6094
insurance_info = req_json['insurance']
95+
useWithoutExtension = req_json['useWithoutExtension']
6196
user = req_json['user']
6297

98+
print("useWithoutExtension:", useWithoutExtension)
99+
63100
envelope_args = {
64101
'signer_client_id': 1000,
65102
'ds_return_url': req_json['callback-url'],
66103
'gateway_account_id': os.environ.get('DS_PAYMENT_GATEWAY_ID'),
67104
'gateway_name': os.environ.get('DS_PAYMENT_GATEWAY_NAME'),
68105
'payment_display_name': os.environ.get('DS_PAYMENT_GATEWAY_DISPLAY_NAME'),
106+
69107
}
70108

71109
try:
72110
# Create envelope with payment
73-
envelope = DsDocument.create_with_payment(
111+
if useWithoutExtension == True:
112+
envelope = DsDocument.create_with_payment_without_extension(
74113
'new-insurance.html', user, insurance_info, envelope_args
75114
)
115+
else:
116+
extensions = json.loads(session.get('extensions'))
117+
envelope = DsDocument.create_with_payment(
118+
'new-insurance.html', user, insurance_info, envelope_args, extensions
119+
)
76120
# Submit envelope to the Docusign
77121
envelope_id = Envelope.send(envelope, session)
78122
except ApiException as exc:

0 commit comments

Comments
 (0)