diff --git a/app/assets/images/uswds.js b/app/assets/images/uswds.js index c3227067a..1ea55bd84 100644 --- a/app/assets/images/uswds.js +++ b/app/assets/images/uswds.js @@ -4,3 +4,4 @@ //= link @uswds/uswds/dist/img/icon-dot-gov.svg //= link @uswds/uswds/dist/img/icon-https.svg //= link @uswds/uswds/dist/img/usa-icons/close.svg +//= link @uswds/uswds/dist/img/usa-icons-bg/search--white.svg diff --git a/app/controllers/api/argyle_controller.rb b/app/controllers/api/argyle_controller.rb index 795f7e375..2de0cd9d8 100644 --- a/app/controllers/api/argyle_controller.rb +++ b/app/controllers/api/argyle_controller.rb @@ -1,6 +1,4 @@ class Api::ArgyleController < ApplicationController - skip_before_action :verify_authenticity_token - USER_TOKEN_ENDPOINT = 'https://api-sandbox.argyle.com/v2/user-tokens'; def update_token diff --git a/app/controllers/cbv_flows_controller.rb b/app/controllers/cbv_flows_controller.rb index d7abde93e..59fece5ff 100644 --- a/app/controllers/cbv_flows_controller.rb +++ b/app/controllers/cbv_flows_controller.rb @@ -10,7 +10,8 @@ def entry def employer_search @argyle_user_token = fetch_and_store_argyle_token - @companies = fetch_employers + @query = search_params[:query] + @employers = @query.blank? ? [] : fetch_employers(@query) end def summary @@ -71,8 +72,12 @@ def fetch_and_store_argyle_token parsed['user_token'] end - def fetch_employers - res = Net::HTTP.get(URI.parse(ITEMS_ENDPOINT), {"Authorization" => "Basic #{Rails.application.credentials.argyle[:api_key]}"}) + def fetch_employers(query = '') + request_params = URI.encode_www_form( + mapping_status: 'verified,mapped', + q: query + ) + res = Net::HTTP.get(URI(ITEMS_ENDPOINT).tap { |u| u.query = request_params }, {"Authorization" => "Basic #{Rails.application.credentials.argyle[:api_key]}"}) parsed = JSON.parse(res) parsed['results'] @@ -84,4 +89,8 @@ def fetch_payroll parsed['results'] end + + def search_params + params.permit(:query) + end end diff --git a/app/javascript/application.js b/app/javascript/application.js index d887608a7..b2d64b4dd 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -7,15 +7,21 @@ import "@uswds/uswds" // make sure USWDS components are wired to their behavior after a Turbo navigation import components from "@uswds/uswds/src/js/components" let initialLoad = true; + document.addEventListener("turbo:load", () => { if (initialLoad) { // initial domready is handled by `import "uswds"` code initialLoad = false return } + const target = document.body Object.keys(components).forEach((key) => { const behavior = components[key] behavior.on(target) }) -}) +}); + +document.addEventListener("turbo:frame-render", () => { + initialLoad = true; +}); diff --git a/app/javascript/controllers/cbv_flows_controller.js b/app/javascript/controllers/cbv_flows_controller.js index e1ecb2d26..330967288 100644 --- a/app/javascript/controllers/cbv_flows_controller.js +++ b/app/javascript/controllers/cbv_flows_controller.js @@ -4,14 +4,13 @@ import * as ActionCable from '@rails/actioncable' import metaContent from "../utilities/meta"; import { loadArgyle, initializeArgyle, updateToken } from "../utilities/argyle" -function toOptionHTML({ value }) { - return ``; -} - export default class extends Controller { - static targets = ["options", "continue", "userAccountId", "fullySynced", "form", "modal"]; - - selection = null; + static targets = [ + "form", + "searchTerms", + "userAccountId", + "modal" + ]; argyle = null; @@ -19,10 +18,6 @@ export default class extends Controller { cable = ActionCable.createConsumer(); - // TODO: information stored on the CbvFlow model can infer whether the paystubs are sync'd - // by checking the value of payroll_data_available_from. We should make that the initial value. - fullySynced = false; - connect() { // check for this value when connected this.argyleUserToken = metaContent('argyle_user_token'); @@ -34,10 +29,7 @@ export default class extends Controller { console.log("Disconnected"); }, received: (data) => { - console.log("Received some data:", data); if (data.event === 'paystubs.fully_synced' || data.event === 'paystubs.partially_synced') { - this.fullySynced = true; - this.formTarget.submit(); } } @@ -54,23 +46,14 @@ export default class extends Controller { console.log(event); } - search(event) { - const input = event.target.value; - this.optionsTarget.innerHTML = [this.optionsTarget.innerHTML, toOptionHTML({ value: input })].join(''); - } - select(event) { - this.selection = event.detail; - - this.continueTarget.disabled = false; + this.submit(event.target.dataset.itemId); } - submit(event) { - event.preventDefault(); - + submit(itemId) { loadArgyle() .then(Argyle => initializeArgyle(Argyle, this.argyleUserToken, { - items: [this.selection.value], + items: [itemId], onAccountConnected: this.onSignInSuccess.bind(this), onAccountError: this.onAccountError.bind(this), // Unsure what these are for! diff --git a/app/javascript/utilities/argyle.js b/app/javascript/utilities/argyle.js index 7a2347208..d564e00c7 100644 --- a/app/javascript/utilities/argyle.js +++ b/app/javascript/utilities/argyle.js @@ -1,5 +1,6 @@ import loadScript from 'load-script'; import metaContent from "./meta"; +import CSRF from './csrf'; const ARGYLE_TOKENS_REFRESH = '/api/argyle/tokens'; @@ -24,7 +25,12 @@ export function initializeArgyle(Argyle, userToken, callbacks) { } export const updateToken = async updateToken => { - const response = await fetch(ARGYLE_TOKENS_REFRESH, { method: 'post' }).then(response => response.json()); + const response = await fetch(ARGYLE_TOKENS_REFRESH, { + method: 'post', + headers: { + 'X-CSRF-Token': CSRF.token, + }, + }).then(response => response.json()); updateToken(response.token); } diff --git a/app/javascript/utilities/csrf.ts b/app/javascript/utilities/csrf.ts new file mode 100644 index 000000000..54a53355f --- /dev/null +++ b/app/javascript/utilities/csrf.ts @@ -0,0 +1,36 @@ +// Borrowed from https://github.com/18F/identity-idp/blob/59bc8bb6c47402f386d9248bfad3c0803f68187e/app/javascript/packages/request/index.ts#L25-L59 +export default class CSRF { + static get token(): string | null { + return this.#tokenMetaElement?.content || null; + } + + static set token(value: string | null) { + if (!value) { + return; + } + + if (this.#tokenMetaElement) { + this.#tokenMetaElement.content = value; + } + + this.#paramInputElements.forEach((input) => { + input.value = value; + }); + } + + static get param(): string | undefined { + return this.#paramMetaElement?.content; + } + + static get #tokenMetaElement(): HTMLMetaElement | null { + return document.querySelector('meta[name="csrf-token"]'); + } + + static get #paramMetaElement(): HTMLMetaElement | null { + return document.querySelector('meta[name="csrf-param"]'); + } + + static get #paramInputElements(): NodeListOf { + return document.querySelectorAll(`input[name="${this.param}"]`); + } +} diff --git a/app/views/cbv_flows/_employer.html.erb b/app/views/cbv_flows/_employer.html.erb new file mode 100644 index 000000000..ec3bbb3a0 --- /dev/null +++ b/app/views/cbv_flows/_employer.html.erb @@ -0,0 +1,31 @@ +<%= turbo_frame_tag 'employers' do %> +
+ <% @employers.each do |employer| %> +
+
+
+

<%= employer['name'] %>

+
+ + +
+
+ <% end %> +
+<% end %> diff --git a/app/views/cbv_flows/employer_search.html.erb b/app/views/cbv_flows/employer_search.html.erb index 90107ae4c..e05d426a0 100644 --- a/app/views/cbv_flows/employer_search.html.erb +++ b/app/views/cbv_flows/employer_search.html.erb @@ -6,39 +6,39 @@

Get payment info from your employer.

- <%= form_with url: next_path, method: :post, data: { 'cbv-flows-target': "form" } do |f| %> - -
- - -
- - <%= f.submit "Continue", - class: "usa-button usa-button--outline", - disabled: "disabled", - type: "submit", - data: { action: "click->cbv-flows#submit", 'cbv-flows-target': "continue" } - %> - +

Search for an employer

+ <%= form_with url: cbv_flow_employer_search_path, method: :get, class: 'usa-search usa-search--big', html: { role: 'search' }, data: { turbo_frame: 'employers', turbo_action: 'advance' } do |f| %> + <%= f.label :query, "Search for your employer", class: "usa-sr-only" %> + <%= f.text_field :query, value: @query, class: "usa-input", type: 'search', data: { "cbv-flows-target": "searchTerms" } %> + + <% end %> + +

Results

+ <%= render partial: 'employer', locals: { employer: @employers } %> + + <%= form_with url: next_path, method: :post, class: 'display-none', data: { 'cbv-flows-target': "form" } do |f| %> + <% end %> +
Welcome to Verify.gov -

This website can help you get approved for Medicaid

+

This website can help you get approved for your benefits.

Get started here by requesting a link from your caseworker.

diff --git a/db/schema.rb b/db/schema.rb index ae447aed5..bbada8ef8 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -27,10 +27,11 @@ t.date "payroll_data_available_from" end - create_table "connected_argyle_accounts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + create_table "connected_argyle_accounts", force: :cascade do |t| t.uuid "user_id", null: false t.uuid "account_id", null: false - t.datetime "created_at", default: -> { "CURRENT_TIMESTAMP" }, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.index ["user_id", "account_id"], name: "index_connected_argyle_accounts_on_user_id_and_account_id", unique: true end diff --git a/doc/compliance/rendered/apps/data.logical.pdf b/doc/compliance/rendered/apps/data.logical.pdf index 0b07fc535..3909a5209 100644 Binary files a/doc/compliance/rendered/apps/data.logical.pdf and b/doc/compliance/rendered/apps/data.logical.pdf differ