| <.settings_tiles> |
| <.tile :if={Plausible.Users.type(@current_user) == :standard} docs="change-email"> |
| <:title> |
| <a id="update-email">Email address</a> |
| </:title> |
| <:subtitle> |
| Change the address associated with your account. |
| </:subtitle> |
| <.form |
| :let={f} |
| action={Routes.settings_path(@conn, :update_email)} |
| for={@email_changeset} |
| method="post" |
| > |
| <.input |
| type="text" |
| name="user[current_email]" |
| value={f.data.email} |
| label="Current email" |
| width="w-1/2" |
| mt?={false} |
| disabled |
| /> |
|
|
| <.input type="email" field={f[:email]} label="New email" width="w-1/2" /> |
|
|
| <.input type="password" field={f[:password]} label="Confirm with password" width="w-1/2" /> |
|
|
| <.button type="submit"> |
| Change email |
| </.button> |
| </.form> |
| </.tile> |
|
|
| <.tile :if={Plausible.Users.type(@current_user) == :sso}> |
| <:title> |
| <a id="view-email">Email address</a> |
| </:title> |
| <:subtitle> |
| Address associated with your account. |
| </:subtitle> |
| <.form :let={f} for={@email_changeset}> |
| <.input |
| type="text" |
| name="user[current_email]" |
| value={f.data.email} |
| label="Current email" |
| width="w-1/2" |
| mt?={false} |
| disabled |
| /> |
| </.form> |
| </.tile> |
|
|
| <.tile :if={Plausible.Users.type(@current_user) == :standard} docs="reset-password"> |
| <:title> |
| <a id="update-password">Password</a> |
| </:title> |
| <:subtitle> |
| Change your password. |
| </:subtitle> |
| <.form |
| :let={f} |
| action={Routes.settings_path(@conn, :update_password)} |
| for={@password_changeset} |
| method="post" |
| > |
| <.input |
| type="password" |
| max_one_error |
| field={f[:old_password]} |
| label="Old password" |
| width="w-1/2" |
| mt?={false} |
| /> |
|
|
| <.input |
| type="password" |
| max_one_error |
| field={f[:password]} |
| label="New password" |
| width="w-1/2" |
| /> |
|
|
| <.input |
| type="password" |
| max_one_error |
| autocomplete="new-password" |
| field={f[:password_confirmation]} |
| label="Confirm new password" |
| width="w-1/2" |
| /> |
|
|
| <div :if={@totp_enabled?} class="mt-6"> |
| <.label for={f[:two_factor_code].id} class="mb-2"> |
| Verify with 2FA Code |
| </.label> |
| <PlausibleWeb.Components.TwoFactor.verify_2fa_input |
| form={f} |
| show_button?={false} |
| field={:two_factor_code} |
| class="w-1/2" |
| /> |
| </div> |
|
|
| <.button type="submit"> |
| Change password |
| </.button> |
| </.form> |
| </.tile> |
|
|
| <.tile docs="2fa"> |
| <:title> |
| <a id="update-2fa">Two-Factor Authentication (2FA)</a> |
| </:title> |
| <:subtitle> |
| Protect your account by adding an extra security step when you log in. |
| </:subtitle> |
|
|
| <div x-data="{disable2FAOpen: false, regenerate2FAOpen: false}"> |
| <div :if={@totp_enabled?}> |
| <.button |
| disabled={Plausible.Users.type(@current_user) == :sso} |
| x-on:click="disable2FAOpen = true; $refs.disable2FAPassword.value = ''" |
| theme="danger" |
| mt?={false} |
| > |
| Disable 2FA |
| </.button> |
|
|
| <p class="mt-2 text-gray-600 text-sm dark:text-gray-400"> |
| Lost your recovery codes? |
| <a |
| href="#update-2fa" |
| x-on:click="regenerate2FAOpen = true; $refs.regenerate2FAPassword.value = ''" |
| class="underline text-indigo-600" |
| > |
| Generate new |
| </a> |
| </p> |
| </div> |
|
|
| <div :if={not @totp_enabled?}> |
| <.form |
| action={Routes.auth_path(@conn, :initiate_2fa_setup)} |
| for={@conn.params} |
| method="post" |
| > |
| <.button type="submit" mt?={false}> |
| Enable 2FA |
| </.button> |
| </.form> |
| </div> |
|
|
| <PlausibleWeb.Components.TwoFactor.modal |
| id="disable-2fa-modal" |
| state_param="disable2FAOpen" |
| form_data={@conn.params} |
| form_target={Routes.auth_path(@conn, :disable_2fa)} |
| title="Disable Two-Factor Authentication?" |
| > |
| <:icon> |
| <Heroicons.shield_exclamation class="h-6 w-6" /> |
| </:icon> |
| <:buttons> |
| <.button type="submit" class="w-full sm:w-auto"> |
| Disable 2FA |
| </.button> |
| </:buttons> |
|
|
| <div class="text-sm mt-2"> |
| Once disabled, verification codes from the authenticator application and current recovery codes will become invalid. 2FA will have to be setup from the start. |
| </div> |
|
|
| <div class="text-sm mt-2"> |
| Enter your password to disable 2FA. |
| </div> |
|
|
| <div class="mt-3"> |
| <.input |
| type="password" |
| id="disable_2fa_password" |
| name="password" |
| value="" |
| placeholder="Enter password" |
| x-ref="disable2FAPassword" |
| /> |
| </div> |
| </PlausibleWeb.Components.TwoFactor.modal> |
|
|
| <PlausibleWeb.Components.TwoFactor.modal |
| id="regenerate-2fa-modal" |
| state_param="regenerate2FAOpen" |
| form_data={@conn.params} |
| form_target={Routes.auth_path(@conn, :generate_2fa_recovery_codes)} |
| onsubmit="document.getElementById('generate-2fa-recovery-button').disabled = true" |
| title="Generate New Recovery Codes?" |
| > |
| <:icon> |
| <Heroicons.key class="h-6 w-6" /> |
| </:icon> |
|
|
| <:buttons> |
| <.button |
| id="generate-2fa-recovery-button" |
| type="submit" |
| class="w-full sm:w-auto [&>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"> |
| Generate New Codes |
| </span> |
|
|
| <span class="label-disabled"> |
| <.spinner class="inline-block h-5 w-5 mr-2 text-white dark:text-gray-400" /> |
| Generating Codes |
| </span> |
| </.button> |
| </:buttons> |
|
|
| <div class="text-sm mt-2"> |
| If you generate new codes, the old ones will become invalid. |
| </div> |
|
|
| <div class="text-sm mt-2"> |
| Enter your password to continue. |
| </div> |
|
|
| <div class="mt-3 w-full"> |
| <.input |
| type="password" |
| id="regenerate_2fa_password" |
| name="password" |
| value="" |
| placeholder="Enter password" |
| x-ref="regenerate2FAPassword" |
| /> |
| </div> |
| </PlausibleWeb.Components.TwoFactor.modal> |
| </div> |
| </.tile> |
|
|
| <.tile docs="login-management"> |
| <:title> |
| <a id="user-sessions">Login management</a> |
| </:title> |
| <:subtitle> |
| Log out other devices. Inactive sessions auto-expire after 14 days. |
| </:subtitle> |
|
|
| <.table rows={@user_sessions}> |
| <:thead> |
| <.th>Device</.th> |
| <.th hide_on_mobile>Last seen</.th> |
| <.th invisible>Actions</.th> |
| </:thead> |
| <:tbody :let={session}> |
| <.td truncate max_width="max-w-40">{session.device}</.td> |
| <.td hide_on_mobile>{Plausible.Auth.UserSessions.last_used_humanize(session)}</.td> |
| <.td :if={@current_user_session.id == session.id} actions>Current session</.td> |
| <.td :if={@current_user_session.id != session.id} actions> |
| <.delete_button |
| href={Routes.settings_path(@conn, :delete_session, session.id)} |
| method="delete" |
| data-confirm="Are you sure you want to log out this session?" |
| /> |
| </.td> |
| </:tbody> |
| </.table> |
| </.tile> |
| </.settings_tiles> |
|
|