Skip to content
Merged
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
25 changes: 25 additions & 0 deletions app/controllers/redirect/help_center_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module Redirect
class HelpCenterController < RedirectController
before_action :validate_help_center_article_params

def show
redirect_to_and_log MarketingSite.help_center_article_url(**article_params)
end

private

def validate_help_center_article_params
begin
return if MarketingSite.valid_help_center_article?(**article_params)
rescue ActionController::ParameterMissing
end

redirect_to root_url
end

def article_params
category, article = params.require([:category, :article])
{ category: category, article: article }
end
end
end
16 changes: 16 additions & 0 deletions app/controllers/redirect/redirect_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module Redirect
class RedirectController < ApplicationController
PERMITTED_LOCATION_PARAMS = [:flow, :step, :location].freeze

private

def location_params
params.permit(*PERMITTED_LOCATION_PARAMS).to_h.symbolize_keys
end

def redirect_to_and_log(url, event: Analytics::EXTERNAL_REDIRECT)
analytics.track_event(event, redirect_url: url, **location_params)
redirect_to(url)
end
end
end
43 changes: 43 additions & 0 deletions app/controllers/redirect/return_to_sp_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
module Redirect
class ReturnToSpController < Redirect::RedirectController
before_action :validate_sp_exists

def cancel
redirect_url = sp_return_url_resolver.return_to_sp_url
redirect_to_and_log redirect_url, event: Analytics::RETURN_TO_SP_CANCEL
end

def failure_to_proof
redirect_url = sp_return_url_resolver.failure_to_proof_url
redirect_to_and_log redirect_url, event: Analytics::RETURN_TO_SP_FAILURE_TO_PROOF
end

private

def sp_return_url_resolver
@sp_return_url_resolver ||= SpReturnUrlResolver.new(
service_provider: current_sp,
oidc_state: sp_request_params[:state],
oidc_redirect_uri: sp_request_params[:redirect_uri],
)
end

def sp_request_params
@request_params ||= begin
if sp_request_url.present?
UriService.params(sp_request_url)
else
{}
end
end
end

def sp_request_url
sp_session[:request_url] || service_provider_request&.url
end

def validate_sp_exists
redirect_to account_url if current_sp.nil?
end
end
end
51 changes: 0 additions & 51 deletions app/controllers/return_to_sp_controller.rb

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useContext } from 'react';
import { useI18n } from '@18f/identity-react-i18n';
import ServiceProviderContext from '../context/service-provider';
import MarketingSiteContext from '../context/marketing-site';
import HelpCenterContext from '../context/help-center';
import useAsset from '../hooks/use-asset';
import Warning from './warning';

Expand All @@ -20,7 +20,7 @@ import Warning from './warning';
*/
function CaptureAdvice({ onTryAgain, isAssessedAsGlare, isAssessedAsBlurry }) {
const { name: spName, getFailureToProofURL } = useContext(ServiceProviderContext);
const { documentCaptureTipsURL } = useContext(MarketingSiteContext);
const { getHelpCenterURL } = useContext(HelpCenterContext);
const { getAssetPath } = useAsset();
const { t } = useI18n();

Expand All @@ -33,7 +33,11 @@ function CaptureAdvice({ onTryAgain, isAssessedAsGlare, isAssessedAsBlurry }) {
troubleshootingOptions={
/** @type {TroubleshootingOption[]} */ ([
{
url: documentCaptureTipsURL,
url: getHelpCenterURL({
category: 'verify-your-identity',
article: 'how-to-add-images-of-your-state-issued-id',
location: 'capture_tips',
}),
text: t('idv.troubleshooting.options.doc_capture_tips'),
isExternal: true,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,35 @@ import { useContext } from 'react';
import { TroubleshootingOptions } from '@18f/identity-components';
import { useI18n } from '@18f/identity-react-i18n';
import ServiceProviderContext from '../context/service-provider';
import MarketingSiteContext from '../context/marketing-site';
import HelpCenterContext from '../context/help-center';

/** @typedef {import('@18f/identity-components/troubleshooting-options').TroubleshootingOption} TroubleshootingOption */

function DocumentCaptureTroubleshootingOptions() {
const { t } = useI18n();
const { documentCaptureTipsURL, supportedDocumentsURL } = useContext(MarketingSiteContext);
const { getHelpCenterURL } = useContext(HelpCenterContext);
const { name: spName, getFailureToProofURL } = useContext(ServiceProviderContext);

return (
<TroubleshootingOptions
heading={t('idv.troubleshooting.headings.having_trouble')}
options={
/** @type {TroubleshootingOption[]} */ ([
documentCaptureTipsURL && {
url: documentCaptureTipsURL,
{
url: getHelpCenterURL({
category: 'verify-your-identity',
article: 'how-to-add-images-of-your-state-issued-id',
location: 'troubleshooting_options',
}),
text: t('idv.troubleshooting.options.doc_capture_tips'),
isExternal: true,
},
supportedDocumentsURL && {
url: supportedDocumentsURL,
{
url: getHelpCenterURL({
category: 'verify-your-identity',
article: 'accepted-state-issued-identification',
location: 'troubleshooting_options',
}),
text: t('idv.troubleshooting.options.supported_documents'),
isExternal: true,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import DocumentCaptureTroubleshootingOptions from './document-capture-troublesho
import PageHeading from './page-heading';
import StartOverOrCancel from './start-over-or-cancel';
import Warning from './warning';
import MarketingSiteContext from '../context/marketing-site';
import HelpCenterContext from '../context/help-center';
import AnalyticsContext from '../context/analytics';
import useDidUpdateEffect from '../hooks/use-did-update-effect';
import './review-issues-step.scss';
Expand Down Expand Up @@ -75,7 +75,7 @@ function ReviewIssuesStep({
const { t } = useI18n();
const { isMobile } = useContext(DeviceContext);
const serviceProvider = useContext(ServiceProviderContext);
const { documentCaptureTipsURL } = useContext(MarketingSiteContext);
const { getHelpCenterURL } = useContext(HelpCenterContext);
const { addPageAction } = useContext(AnalyticsContext);
const selfieError = errors.find(({ field }) => field === 'selfie')?.error;
const [hasDismissed, setHasDismissed] = useState(remainingAttempts === Infinity);
Expand Down Expand Up @@ -164,7 +164,11 @@ function ReviewIssuesStep({
troubleshootingOptions={
/** @type {TroubleshootingOption[]} */ ([
{
url: documentCaptureTipsURL,
url: getHelpCenterURL({
category: 'verify-your-identity',
article: 'how-to-add-images-of-your-state-issued-id',
location: 'post_submission_warning',
}),
text: t('idv.troubleshooting.options.doc_capture_tips'),
isExternal: true,
},
Expand Down
56 changes: 56 additions & 0 deletions app/javascript/packages/document-capture/context/help-center.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { createContext } from 'react';
import { addSearchParams } from '@18f/identity-url';

/** @typedef {import('react').ReactNode} ReactNode */

/**
* @typedef HelpCenterURLParameters
*
* @prop {string} category
* @prop {string} article
* @prop {string} location
*/

/**
* @typedef {(params: HelpCenterURLParameters) => string} GetHelpCenterURL
*/

/**
* @typedef HelpCenterContext
*
* @prop {string} helpCenterRedirectURL
* @prop {GetHelpCenterURL} getHelpCenterURL
*/

const HelpCenterContext = createContext(
/** @type {HelpCenterContext} */ ({
helpCenterRedirectURL: '',
getHelpCenterURL: (params) => addSearchParams('', params),
}),
);

HelpCenterContext.displayName = 'HelpCenterContext';

/**
* @typedef HelpCenterContextProviderProps
*
* @prop {Omit<HelpCenterContext, 'getHelpCenterURL'>} value
* @prop {ReactNode} children
*/

/**
* @param {HelpCenterContextProviderProps} props
*/
function HelpCenterContextProvider({ value, children }) {
/** @type {GetHelpCenterURL} */
const getHelpCenterURL = (params) => addSearchParams(value.helpCenterRedirectURL, params);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to me this seems more like a build method than a get method?

Copy link
Copy Markdown
Contributor Author

@aduth aduth Dec 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to me this seems more like a build method than a get method?

Hm, could you explain more? Is the idea that a get-prefixed method would only return a value directly, not derive something? I guess I've not made much a distinction on this previously, or at least would be happy to just offer up the value directly if that's what a "get" would imply.

I don't feel too strongly, but more worry that we haven't made much of a distinction in the past, so an argument could be made to the point of "consistency for consistency's sake" 🤷

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in my head, "get" is like "retrieve" like a load from a database or a hash or something

whereas in this case, we're not retrieving anything, we're building a new string based on some immediate values

But this is just personal style, like you said, we don't have a codebase style so I really don't feel that strongly about enforcing it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I think I'll plan to keep it as-is for now, though perhaps separately we can think about / document naming conventions around methods.


return (
<HelpCenterContext.Provider value={{ ...value, getHelpCenterURL }}>
{children}
</HelpCenterContext.Provider>
);
}

export default HelpCenterContext;
export { HelpCenterContextProvider as Provider };
2 changes: 1 addition & 1 deletion app/javascript/packages/document-capture/context/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export { default as AppContext } from './app';
export { default as AssetContext } from './asset';
export { default as DeviceContext } from './device';
export { default as AcuantContext, Provider as AcuantContextProvider } from './acuant';
export { default as MarketingSiteContext } from './marketing-site';
export { default as HelpCenterContext, Provider as HelpCenterContextProvider } from './help-center';
export { default as UploadContext, Provider as UploadContextProvider } from './upload';
export {
default as ServiceProviderContext,
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createContext, useMemo } from 'react';
import { addSearchParams } from '@18f/identity-url';

/** @typedef {import('react').ReactNode} ReactNode */

Expand Down Expand Up @@ -37,11 +38,7 @@ function ServiceProviderContextProvider({ value, children }) {
const mergedValue = useMemo(
() => ({
...value,
getFailureToProofURL(location) {
const url = new URL(value.failureToProofURL);
url.searchParams.set('location', location);
return url.toString();
},
getFailureToProofURL: (location) => addSearchParams(value.failureToProofURL, { location }),
}),
[value],
);
Expand Down
29 changes: 29 additions & 0 deletions app/javascript/packages/url/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Given a URL or a string fragment of search parameters and an object of parameters, returns a
* new URL or search parameters with the parameters added.
*
* @param {string} urlOrParams Original URL or search parameters.
* @param {Record<string, *>} params Search parameters to add.
*
* @return {string} Modified URL or search parameters.
*/
export function addSearchParams(urlOrParams, params) {
/** @type {URL|URLSearchParams} */
let parsedURLOrParams;

/** @type {URLSearchParams} */
let searchParams;

try {
parsedURLOrParams = new URL(urlOrParams);
searchParams = parsedURLOrParams.searchParams;
} catch {
parsedURLOrParams = new URLSearchParams(urlOrParams);
searchParams = parsedURLOrParams;
}

Object.entries(params).forEach(([key, value]) => searchParams.set(key, value));

const result = parsedURLOrParams.toString();
return parsedURLOrParams instanceof URLSearchParams ? `?${result}` : result;
}
Loading