Skip to content

Commit

Permalink
⚙️ User management: change password
Browse files Browse the repository at this point in the history
  • Loading branch information
RTLS committed Dec 27, 2024
1 parent 0ebf223 commit 6562196
Show file tree
Hide file tree
Showing 6 changed files with 287 additions and 151 deletions.
13 changes: 13 additions & 0 deletions assets/svelte/components/Sidenav.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,19 @@
{/each}
</Command.Group>
<Command.Separator />
<Command.Group heading="User settings">
<a
href="/users/settings"
data-phx-link="redirect"
data-phx-link-state="push"
>
<Command.Item class="cursor-pointer">
<Cog class="mr-2 h-4 w-4" />
<span>Manage user</span>
</Command.Item>
</a>
</Command.Group>
<Command.Separator />
<Command.Group heading="Account settings">
<Command.Item
onSelect={() => openCreateAccount()}
Expand Down
194 changes: 194 additions & 0 deletions assets/svelte/settings/UserSettings.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button";
import * as Card from "$lib/components/ui/card";
import { Input } from "$lib/components/ui/input";
import * as Dialog from "$lib/components/ui/dialog";
import { Label } from "$lib/components/ui/label";
import { Info, User } from "lucide-svelte";
import * as Alert from "$lib/components/ui/alert";
export let parent: string;
export let live;
export let currentUser: {
id: string;
email: string;
auth_provider: string;
};
let showDeleteConfirmDialog = false;
let deleteConfirmDialogLoading = false;
let passwordChangeEnabled = currentUser.auth_provider === "identity";
let changePasswordLoading = false;
let currentPassword = "";
let newPassword = "";
let newPasswordConfirmation = "";
let changePasswordErrors: any = {};
function handleChangePassword(event: SubmitEvent) {
event.preventDefault();
changePasswordLoading = true;
changePasswordErrors = {};
live.pushEventTo(
`#${parent}`,
"change_password",
{
current_password: currentPassword,
new_password: newPassword,
new_password_confirmation: newPasswordConfirmation,
},
(res: any) => {
changePasswordLoading = false;
if (res.ok) {
currentPassword = "";
newPassword = "";
newPasswordConfirmation = "";
} else {
changePasswordErrors = res.errors;
}
},
);
}
function handleDeleteUser() {
deleteConfirmDialogLoading = true;
live.pushEventTo(`#${parent}`, "delete_user", {}, (res: any) => {
deleteConfirmDialogLoading = false;
if (res.ok) {
showDeleteConfirmDialog = false;
}
});
}
</script>

<div>
<div class="flex items-center mb-4">
<User class="h-6 w-6 mr-2" />
<h1 class="text-2xl font-bold">User Settings</h1>
</div>

<div class="flex flex-col gap-6 container w-auto">
<Card.Root>
<Card.Header>
<Card.Title>Email address</Card.Title>
</Card.Header>
<Card.Content>
<p class="text-gray-600">{currentUser.email}</p>
</Card.Content>
</Card.Root>

<Card.Root>
<Card.Header>
<Card.Title>Change password</Card.Title>
</Card.Header>
<Card.Content>
{#if currentUser.auth_provider === "github"}
<Alert.Root variant="warning">
<Alert.Description>
<div class="flex items-center gap-2">
<Info class="h-4 w-4" />
<p>
You cannot change your password when using GitHub
authentication.
</p>
</div>
</Alert.Description>
</Alert.Root>
{/if}
<form on:submit={handleChangePassword} class="mt-4 space-y-4">
<div class="grid w-full items-center gap-1.5">
<Label for="current-password">Current password</Label>
<Input
type="password"
id="current-password"
bind:value={currentPassword}
disabled={!passwordChangeEnabled}
/>
{#if changePasswordErrors.current_password}
<p class="text-sm text-destructive">
{changePasswordErrors.current_password[0]}
</p>
{/if}
</div>
<div class="grid w-full items-center gap-1.5">
<Label for="new-password">New password</Label>
<Input
type="password"
id="new-password"
bind:value={newPassword}
disabled={!passwordChangeEnabled}
/>
{#if changePasswordErrors.password}
<p class="text-sm text-destructive">
{changePasswordErrors.password[0]}
</p>
{/if}
</div>
<div class="grid w-full items-center gap-1.5">
<Label for="confirm-password">Confirm new password</Label>
<Input
type="password"
id="confirm-password"
bind:value={newPasswordConfirmation}
disabled={!passwordChangeEnabled}
/>
{#if changePasswordErrors.password_confirmation}
<p class="text-sm text-destructive">
{changePasswordErrors.password_confirmation[0]}
</p>
{/if}
</div>
<Button
type="submit"
disabled={changePasswordLoading || !passwordChangeEnabled}
loading={changePasswordLoading}
>
Change password
</Button>
</form>
</Card.Content>
</Card.Root>

<!-- <Card.Root>
<Card.Header>
<Card.Title>Delete user</Card.Title>
</Card.Header>
<Card.Content>
<div class="flex items-center justify-between space-x-4">
<p>Permanently delete your user. This action cannot be undone.</p>
<Button
variant="destructive"
on:click={() => (showDeleteConfirmDialog = true)}
>
Delete user
</Button>
</div>
</Card.Content>
</Card.Root> -->
</div>
</div>

<Dialog.Root bind:open={showDeleteConfirmDialog}>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>Are you sure you want to delete your account?</Dialog.Title>
<Dialog.Description>This action cannot be undone.</Dialog.Description>
</Dialog.Header>
<Dialog.Footer>
<Button
variant="outline"
on:click={() => (showDeleteConfirmDialog = false)}
>
Cancel
</Button>
<Button
variant="destructive"
on:click={handleDeleteUser}
disabled={deleteConfirmDialogLoading}
loading={deleteConfirmDialogLoading}
>
Delete
</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Root>
18 changes: 16 additions & 2 deletions lib/sequin/accounts/accounts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -204,10 +204,16 @@ defmodule Sequin.Accounts do
%Ecto.Changeset{data: %User{}}
"""
def change_user_email(%User{auth_provider: :identity} = user, attrs \\ %{}) do
def change_user_email(user, attrs \\ %{})

def change_user_email(%User{auth_provider: :identity} = user, attrs) do
User.email_changeset(user, attrs, validate_email: false)
end

def change_user_email(%User{auth_provider: :github} = user, attrs) do
user |> User.email_changeset(attrs) |> Ecto.Changeset.add_error(:email, "GitHub users cannot change their email")
end

@doc """
Emulates that the email will change without actually changing
it in the database.
Expand Down Expand Up @@ -283,10 +289,18 @@ defmodule Sequin.Accounts do
%Ecto.Changeset{data: %User{}}
"""
def change_user_password(%User{auth_provider: :identity} = user, attrs \\ %{}) do
def change_user_password(user, attrs \\ %{})

def change_user_password(%User{auth_provider: :identity} = user, attrs) do
User.password_changeset(user, attrs, hash_password: false)
end

def change_user_password(%User{auth_provider: :github} = user, attrs) do
user
|> User.password_changeset(attrs)
|> Ecto.Changeset.add_error(:password, "GitHub users cannot change their password")
end

@doc """
Updates the user password.
Expand Down
Loading

0 comments on commit 6562196

Please sign in to comment.