File size: 5,838 Bytes
8da2481
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
defmodule PlausibleWeb.Components.TwoFactor do
  @moduledoc """
  Reusable components specific to 2FA
  """
  use PlausibleWeb, :component

  attr :text, :string, required: true
  attr :scale, :integer, default: 4

  def qr_code(assigns) do
    qr_code =
      assigns.text
      |> EQRCode.encode()
      |> EQRCode.svg(%{width: 160})

    assigns = assign(assigns, :code, qr_code)

    ~H"""
    {Phoenix.HTML.raw(@code)}
    """
  end

  attr :id, :string, default: "verify-button"
  attr :form, :any, required: true
  attr :field, :any, required: true
  attr :class, :string, default: ""
  attr :show_button?, :boolean, default: true
  attr :autofocus?, :boolean, default: false
  attr :error, :string, default: nil

  def verify_2fa_input(assigns) do
    assigns = assign(assigns, :field, assigns[:form][assigns[:field]])

    ~H"""
    <div class={[@class, "flex flex-col gap-y-6 items-center"]}>
      <div class="flex flex-col gap-y-2 w-full">
        <.otp_input
          field={@field}
          length={6}
          oninvalid={@show_button? && "document.getElementById('#{@id}').disabled = false"}
          autofocus={@autofocus? && "autofocus"}
        />
        <p :if={@error} class="text-xs text-red-500 text-center">
          {@error}
        </p>
      </div>
      <.button
        :if={@show_button?}
        type="submit"
        id={@id}
        mt?={false}
        class="w-full [&>span.label-enabled]:block [&>span.label-disabled]:hidden [&[disabled]>span.label-enabled]:hidden [&[disabled]>span.label-disabled]:block"
      >
        <span class="label-enabled pointer-events-none">
          Verify
        </span>

        <span class="label-disabled">
          <.spinner class="inline-block h-5 w-5 mr-2 text-white dark:text-gray-400" /> Verifying...
        </span>
      </.button>
    </div>
    """
  end

  attr :id, :string, required: true
  attr :state_param, :string, required: true
  attr :form_data, :any, required: true
  attr :form_target, :string, required: true
  attr :onsubmit, :string, default: nil
  attr :title, :string, required: true

  slot :icon, required: true
  slot :inner_block, required: true
  slot :buttons, required: true

  def modal(assigns) do
    ~H"""
    <div
      id={@id}
      x-cloak
      x-show={@state_param}
      x-on:keyup.escape.window={"#{@state_param} = false"}
      class="fixed z-10 inset-0 overflow-y-auto"
      aria-labelledby="modal-title"
      role="dialog"
      aria-modal="true"
    >
      <div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
        <div
          x-show={@state_param}
          x-transition:enter="transition ease-out duration-300"
          x-transition:enter-start="opacity-0"
          x-transition:enter-end="opacity-100"
          x-transition:leave="transition ease-in duration-200"
          x-transition:leave-start="opacity-100"
          x-transition:leave-end="opacity-0"
          class="fixed inset-0 bg-gray-500/75 dark:bg-gray-800/75 transition-opacity"
          aria-hidden="true"
          x-on:click={"#{@state_param} = false"}
        >
        </div>
        <!-- This element is to trick the browser into centering the modal contents. -->
        <span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
          &#8203;
        </span>

        <div
          x-show={@state_param}
          x-transition:enter="transition ease-out duration-300"
          x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
          x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
          x-transition:leave="transition ease-in duration-200"
          x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
          x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
          class="relative inline-block align-bottom bg-white dark:bg-gray-900 rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
        >
          <.form :let={f} for={@form_data} action={@form_target} onsubmit={@onsubmit}>
            <div class="bg-white dark:bg-gray-850 px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
              <div class="hidden sm:block absolute top-0 right-0 pt-4 pr-4">
                <a
                  href="#"
                  x-on:click.prevent={"#{@state_param} = false"}
                  class="bg-white dark:bg-gray-800 rounded-md text-gray-400 dark:text-gray-500 hover:text-gray-500 dark:hover:text-gray-400 focus:outline-hidden"
                >
                  <span class="sr-only">Close</span>
                  <Heroicons.x_mark class="h-6 w-6" />
                </a>
              </div>
              <div class="sm:flex sm:items-start">
                <div class="mx-auto shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-green-100 sm:mx-0 sm:h-10 sm:w-10">
                  {render_slot(@icon)}
                </div>
                <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left text-gray-900 dark:text-gray-100">
                  <h3 class="text-lg leading-6 font-medium" id="modal-title">
                    {@title}
                  </h3>

                  {render_slot(@inner_block, f)}
                </div>
              </div>
            </div>
            <div class="bg-gray-50 dark:bg-gray-850 px-4 py-3 sm:px-9 sm:flex sm:flex-row-reverse">
              {render_slot(@buttons)}
              <.button
                type="button"
                x-on:click={"#{@state_param} = false"}
                class="w-full sm:w-auto mr-2"
                theme="secondary"
              >
                Cancel
              </.button>
            </div>
          </.form>
        </div>
      </div>
    </div>
    """
  end
end