Skip to content
Open
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
2 changes: 2 additions & 0 deletions client/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import Contacts from './pages/Contacts/Contacts.jsx';
import ContactForm from './pages/ContactForm/ContactForm.jsx';
import DemoAccountInitializer from './services/demoAccountInitializer.js';
import createPrefixedLogger from './helpers/logger.js';
import Navigator from './pages/Navigator/Navigator.jsx';

const logger = createPrefixedLogger('App');

Expand Down Expand Up @@ -61,6 +62,7 @@ function App() {
<Route path={AppRoute.CONTACTS} element={<Contacts />} />
<Route path={AppRoute.ADD_CONTACT} element={<ContactForm />} />
<Route path={AppRouteFactory.createEditContact(':contactId')} element={<ContactForm />} />
<Route path={AppRoute.NAVIGATOR} element={<Navigator />} />
</Routes>
);
}
Expand Down
4 changes: 3 additions & 1 deletion client/src/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { clearAllState } from '../store/actions';

const isDev = process.env.NODE_ENV === 'development';
const apiUrl = isDev ? process.env.BACKEND_DEV_HOST : process.env.BACKEND_PROD_HOST;

const clearState = async () => {
store.dispatch(clearAllState());
await persistor.purge();
Expand Down Expand Up @@ -79,4 +78,7 @@ export const api = Object.freeze({
contacts: {
send: data => instance.post('/contacts/send', data),
},
navigator: {
getData: () => instance.get('/navigator/data'),
},
});
10 changes: 9 additions & 1 deletion client/src/assets/text.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
"tabs": {
"dashboard": "Dashboard",
"templates": "Templates",
"contacts": "Contacts"
"contacts": "Contacts",
"navigator": "Navigator"
},
"envelopes": {
"emptyList": "Create some Embedded Sending transactions and they will start showing up here",
Expand Down Expand Up @@ -140,5 +141,12 @@
"github": "https://github.com/docusign/sample-app-embeddedsending-node",
"createsandbox": "https://go.docusign.com/o/sandbox/",
"learnmore": "https://developers.docusign.com/docs/esign-rest-api/esign101/concepts/embedding/embed-sender-correct-views/"
},
"navigator": {
"behindScenes": {
"scenarioOverview": "This scenario demonstrates a simple navigation widget that allows users to select different options.",
"codeFlow": "View the source code: [navigatorController.js](https://github.com/docusign/sample-app-embeddedsending-node/blob/main/server/controllers/navigatorController.js)",
"step1": "The application fetches navigation options from the backend API and displays them in an interactive widget."
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ const SidebarLayoutFrame = ({ children }) => {
<NavLink to={AppRoute.CONTACTS} className={getNavLinkClass}>
<ContactsIcon className={styles.sidebarLinkIcon} /> {t.tabs.contacts}
</NavLink>
<NavLink to={AppRoute.NAVIGATOR} className={getNavLinkClass}>
<ContactsIcon className={styles.sidebarLinkIcon} /> {t.tabs.navigator}
</NavLink>
</div>
<main className={styles.mainContent}>{children}</main>
</div>
Expand Down
1 change: 1 addition & 0 deletions client/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const AppRoute = {
CONTACTS: '/contacts',
ADD_CONTACT: '/contacts/new',
EDIT_CONTACT_PREFIX: '/contacts/edit',
NAVIGATOR: '/navigator'
};

export const AppRouteFactory = {
Expand Down
20 changes: 20 additions & 0 deletions client/src/pages/Navigator/Navigator.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import withAuth from '../../hocs/withAuth/withAuth.jsx';
import AuthLayout from '../../components/AuthLayout/AuthLayout.jsx';
import t from '../../helpers/t.js';
import SidebarLayoutFrame from '../../components/SidebarLayoutFrame/SidebarLayoutFrame.jsx';
import NavigatorContent from './components/NavigatorContent.jsx';
import NavigatorBehindScenes from './components/NavigatorBehindScenes.jsx';

const NavigatorNonAuth = () => {
return (
<AuthLayout behindScenesContentNode={<NavigatorBehindScenes />}>
<SidebarLayoutFrame>
<h1>{t.tabs.navigator}</h1>
<NavigatorContent />
</SidebarLayoutFrame>
</AuthLayout>
);
};

const Navigator = withAuth(NavigatorNonAuth);
export default Navigator;
22 changes: 22 additions & 0 deletions client/src/pages/Navigator/components/NavigatorBehindScenes.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import BehindScenesContainer from '../../../components/BehindScenes/BehindScenesContainer';
import BehindScenesSection from '../../../components/BehindScenes/BehindScenesSection';
import TextWithAnchorsMarkup from '../../../components/BehindScenes/TextWithAnchorsMarkup';
import t from '../../../helpers/t';

const NavigatorBehindScenes = () => {
return (
<BehindScenesContainer>
<BehindScenesSection title={t.behindScenesCommon.scenarioOverview}>
{t.navigator.behindScenes.scenarioOverview}
</BehindScenesSection>
<BehindScenesSection title={t.behindScenesCommon.codeFlow}>
<TextWithAnchorsMarkup>{t.navigator.behindScenes.codeFlow}</TextWithAnchorsMarkup>
</BehindScenesSection>
<BehindScenesSection title={t.behindScenesCommon.step1}>
<TextWithAnchorsMarkup>{t.navigator.behindScenes.step1}</TextWithAnchorsMarkup>
</BehindScenesSection>
</BehindScenesContainer>
);
};

export default NavigatorBehindScenes;
80 changes: 80 additions & 0 deletions client/src/pages/Navigator/components/NavigatorContent.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { useState, useEffect, useRef } from 'react';
import { useSelector } from 'react-redux';
import { api } from '../../../api';
import styles from './NavigatorContent.module.css';

const NavigatorContent = () => {
const [error, setError] = useState(null);
const iframe = useRef(null);
const userEmail = useSelector(state => state.auth.userEmail);
const userName = useSelector(state => state.auth.userName);

useEffect(() => {
const initNavigator = async () => {
try {
const response = await api.navigator.getData();
const { token } = response.data;
console.log('Navigator token:', token);
if (!iframe.current) return;

// Configure the iframe
iframe.current.src = 'https://apps-d.docusign.com/navigator-for-partners';

const handleIframeLoad = () => {
const dataToSend = {
message: 'Embedded-Navigator-View',
authInfo: { access_token: token },
userInfo: {
sub: '13f69e9e-d3ec-4cfe-b551-5ff33b1751c8',
name: "prudhvi nag",
email: "prudhvinag@dsxtr.com",
accounts: [{
account_id: 'c9b55f81-6261-4a6e-b53f-59a2d341c10f',
name: userName,
is_default: true,
account_name: userName,
base_uri: 'https://demo.docusign.net',
}]
},
envelopeIds: [],
currentAccount: 'c9b55f81-6261-4a6e-b53f-59a2d341c10f',
timestamp: new Date().toISOString()
};

iframe.current.contentWindow.postMessage(
dataToSend,
iframe.current.src
);
};

iframe.current.onload = handleIframeLoad;

} catch (err) {
setError(err.message);
console.error('Navigator initialization error:', err);
}
};

initNavigator();
}, [userEmail, userName]);

if (error) return <div className={styles.error}>Error: {error}</div>;

return (
<div className={styles.container}>
<div className={styles.widget}>
<h2 className={styles.widgetTitle}>DocuSign Navigator</h2>
<div className={styles.iframeContainer}>
<iframe
ref={iframe}
id="navigatorFrame"
className={styles.navigatorFrame}
title="DocuSign Navigator"
/>
</div>
</div>
</div>
);
};

export default NavigatorContent;
70 changes: 70 additions & 0 deletions client/src/pages/Navigator/components/NavigatorContent.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
.container {
padding: 20px;
}

.widget {
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 24px;
height: calc(100vh - 120px);
display: flex;
flex-direction: column;
}

.widgetTitle {
color: #333;
font-size: 1.5rem;
margin-bottom: 20px;
}

.widgetContent {
display: flex;
flex-direction: column;
gap: 20px;
}

.content {
padding: 16px;
background: #f8f9fa;
border-radius: 4px;
}

.label {
color: #495057;
font-weight: 600;
margin-bottom: 8px;
}

.token {
font-family: monospace;
word-break: break-all;
padding: 12px;
background: #e9ecef;
border-radius: 4px;
border: 1px solid #dee2e6;
}

.loading {
text-align: center;
padding: 20px;
}

.error {
color: red;
padding: 20px;
}

.iframeContainer {
flex: 1;
min-height: 0;
position: relative;
}

.navigatorFrame {
width: 100%;
height: 100%;
border: none;
border-radius: 4px;
background: #f8f9fa;
}
3 changes: 2 additions & 1 deletion server/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const BackendRoute = {
TEMPLATE: '/api/templates',
CONTACT: '/api/contacts',
ENVELOPE: '/api/envelopes',
NAVIGATOR: '/api/navigator'
};

const AuthMethod = {
Expand All @@ -23,7 +24,7 @@ const ViewType = {

// https://developers.docusign.com/platform/auth/reference/scopes/
// signature - Required to call most eSignature REST API endpoints
const EMBEDDED_SENDING_SCOPES = ['signature', 'impersonation'];
const EMBEDDED_SENDING_SCOPES = ['signature', 'impersonation', 'cds_read', 'adm_store_unified_repo_read'];

const ALLOWED_FOR_EDIT_ENVELOPE_STATUSES = ['sent', 'correct'];

Expand Down
23 changes: 23 additions & 0 deletions server/controllers/navigatorController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const createPrefixedLogger = require('../utils/logger');
const logger = createPrefixedLogger('[NavigatorController]');

const getData = async (req, res, next) => {
try {
// Get token based on auth method
const token = req.user?.accessToken || req.session?.accessToken;
console.log('Access Token:', token);
const data = {
status: 'success',
message: 'Access token retrieved successfully',
token: token || 'No token available'
};
res.json(data);
} catch (error) {
logger.error('Error in getData:', error);
next(error);
}
};

module.exports = {
getData
};
9 changes: 9 additions & 0 deletions server/routes/navigatorRouter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const { Router } = require('express');
const navigatorController = require('../controllers/navigatorController');
const authMiddleware = require('../middlewares/authMiddleware');

const router = Router();

router.get('/data', authMiddleware, navigatorController.getData);

module.exports = router;
2 changes: 2 additions & 0 deletions server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const authRouter = require('./routes/authRouter');
const templatesRouter = require('./routes/templatesRouter');
const contactsRouter = require('./routes/contactsRouter');
const envelopesRouter = require('./routes/envelopesRouter');
const navigatorRouter = require('./routes/navigatorRouter');
const createPrefixedLogger = require('./utils/logger');
const resolveAuthController = require('./utils/authControllerResolver');

Expand Down Expand Up @@ -55,6 +56,7 @@ app.use(BackendRoute.AUTH, authRouter);
app.use(BackendRoute.TEMPLATE, templatesRouter);
app.use(BackendRoute.CONTACT, contactsRouter);
app.use(BackendRoute.ENVELOPE, envelopesRouter);
app.use(BackendRoute.NAVIGATOR, navigatorRouter);

async function start() {
try {
Expand Down