feat: proper notification (error/success) handling
This commit is contained in:
parent
37fe85a7b4
commit
8f9f5c76cb
@ -5,11 +5,19 @@
|
|||||||
--light-foreground: #e0e0e0;
|
--light-foreground: #e0e0e0;
|
||||||
--light-accent: #303052;
|
--light-accent: #303052;
|
||||||
--light-text: rgb(34, 40, 49);
|
--light-text: rgb(34, 40, 49);
|
||||||
|
--light-error-text: #4b0000;
|
||||||
|
--light-error-background: #fcaaaae1;
|
||||||
|
--light-success-text: #004b0a;
|
||||||
|
--light-success-background: #adfcaae1;
|
||||||
|
|
||||||
--dark-background: #181818;
|
--dark-background: #181818;
|
||||||
--dark-foreground: #222222;
|
--dark-foreground: #222222;
|
||||||
--dark-accent: #bebff7;
|
--dark-accent: #bebff7;
|
||||||
--dark-text: rgb(238, 238, 238);
|
--dark-text: rgb(238, 238, 238);
|
||||||
|
--dark-error-text: #fcadaa;
|
||||||
|
--dark-error-background: #4b0000e0;
|
||||||
|
--dark-success-text: #adfcaa;
|
||||||
|
--dark-success-background: #004b0ae0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Scrollbar width */
|
/* Scrollbar width */
|
||||||
@ -79,7 +87,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
@apply rounded-md bg-[var(--light-accent)] px-4 py-2 text-[var(--light-background)] transition-all duration-200;
|
@apply cursor-pointer rounded-md bg-[var(--light-accent)] px-4 py-2 text-[var(--light-background)] transition-all duration-200;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
button:hover {
|
||||||
@ -131,20 +139,20 @@
|
|||||||
|
|
||||||
/* Error messages */
|
/* Error messages */
|
||||||
.error {
|
.error {
|
||||||
@apply rounded-lg bg-red-100 p-3 text-sm text-red-500;
|
@apply flex items-center justify-between rounded-lg bg-[var(--light-error-background)] p-3 text-sm text-[var(--light-error-text)];
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .error {
|
.dark .error {
|
||||||
@apply bg-red-900/30 text-red-300;
|
@apply bg-[var(--dark-error-background)] text-[var(--dark-error-text)];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Success messages */
|
/* Success messages */
|
||||||
.success {
|
.success {
|
||||||
@apply rounded-lg bg-green-100 p-3 text-sm text-green-500;
|
@apply flex items-center justify-between rounded-lg bg-[var(--light-success-background)] p-3 text-sm text-[var(--light-success-text)];
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .success {
|
.dark .success {
|
||||||
@apply bg-green-900/30 text-green-300;
|
@apply bg-[var(--dark-success-background)] text-[var(--dark-success-text)];
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
@ -185,13 +193,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
@apply fixed flex h-full flex-col overflow-hidden border-r border-[var(--light-foreground)] bg-[var(--light-foreground)] transition-all duration-300;
|
@apply fixed z-10 flex h-full flex-col overflow-hidden border-r border-[var(--light-foreground)] bg-[var(--light-foreground)] transition-all duration-300;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .sidebar {
|
.dark .sidebar {
|
||||||
@apply border-[var(--dark-foreground)] bg-[var(--dark-foreground)];
|
@apply border-[var(--dark-foreground)] bg-[var(--dark-foreground)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar-header {
|
||||||
|
/* Should align with the navbar height for visual consistency */
|
||||||
|
@apply h-20;
|
||||||
|
}
|
||||||
|
|
||||||
.sidebar-header,
|
.sidebar-header,
|
||||||
.sidebar-footer {
|
.sidebar-footer {
|
||||||
@apply border-[var(--light-text)]/20 p-4;
|
@apply border-[var(--light-text)]/20 p-4;
|
||||||
@ -243,27 +256,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-item-delete {
|
.sidebar-item-delete {
|
||||||
@apply flex h-6.5 w-6.5 cursor-pointer items-center justify-center rounded-lg border-1 border-[var(--light-accent)]/20 bg-transparent p-1;
|
@apply flex h-6.5 w-6.5 items-center justify-center rounded-lg border-1 border-[var(--light-accent)]/20 bg-transparent p-1 text-[var(--light-accent)]/50 hover:text-[var(--light-accent)];
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .sidebar-item-delete {
|
.dark .sidebar-item-delete {
|
||||||
@apply border-[var(--dark-accent)]/20;
|
@apply border-[var(--dark-accent)]/20 text-[var(--dark-accent)]/50 hover:text-[var(--dark-accent)];
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-item-delete > svg {
|
|
||||||
@apply text-[var(--light-accent)]/50;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .sidebar-item-delete > svg {
|
|
||||||
@apply text-[var(--dark-accent)]/50;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-item-delete:hover > svg {
|
|
||||||
@apply text-[var(--light-accent)] transition-colors delay-100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .sidebar-item-delete:hover > svg {
|
|
||||||
@apply text-[var(--dark-accent)];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-divider {
|
.sidebar-divider {
|
||||||
@ -304,7 +301,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.versions-dropdown-item {
|
.versions-dropdown-item {
|
||||||
@apply w-full cursor-pointer bg-[var(--light-foreground)] p-3 transition-colors hover:bg-[var(--light-background)];
|
@apply w-full bg-[var(--light-foreground)] p-3 transition-colors hover:bg-[var(--light-background)];
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .versions-dropdown-item {
|
.dark .versions-dropdown-item {
|
||||||
@ -521,15 +518,15 @@
|
|||||||
|
|
||||||
/* Settings modal */
|
/* Settings modal */
|
||||||
.modal-backdrop {
|
.modal-backdrop {
|
||||||
@apply fixed inset-0 z-50 flex items-center justify-center backdrop-blur-xs;
|
@apply fixed inset-0 z-40 flex items-center justify-center backdrop-blur-xs;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content {
|
.modal-content {
|
||||||
@apply mx-4 max-h-[90vh] w-full max-w-md overflow-y-auto rounded-lg bg-[var(--light-background)] shadow-lg;
|
@apply mx-4 max-h-[90vh] w-full max-w-md overflow-y-auto rounded-lg border-2 border-[var(--light-accent)]/10 bg-[var(--light-background)] shadow-lg;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .modal-content {
|
.dark .modal-content {
|
||||||
@apply bg-[var(--dark-background)];
|
@apply border-[var(--dark-accent)]/10 bg-[var(--dark-background)];
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-section {
|
.modal-section {
|
||||||
@ -541,11 +538,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.modal-close-button {
|
.modal-close-button {
|
||||||
@apply text-[var(--light-background)]/60 hover:text-[var(--light-background)];
|
@apply h-8 w-8 items-center justify-center rounded-md border-1 border-[var(--light-accent)]/20 bg-transparent p-1 text-[var(--light-accent)]/50 hover:text-[var(--light-accent)];
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .modal-close-button {
|
.dark .modal-close-button {
|
||||||
@apply text-[var(--dark-background)]/60 hover:text-[var(--dark-background)];
|
@apply border-[var(--dark-accent)]/20 text-[var(--dark-accent)]/50 hover:text-[var(--dark-accent)];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Loading spinner */
|
/* Loading spinner */
|
||||||
@ -615,12 +612,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-error-popup {
|
.main-info-popup {
|
||||||
|
/* Z-value should be set so that this is on top of the modal's background blur */
|
||||||
@apply fixed top-4 right-4 z-50 max-w-md;
|
@apply fixed top-4 right-4 z-50 max-w-md;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.main-info-popup-button {
|
||||||
|
@apply ml-2 h-4 w-4 items-center justify-center rounded-md bg-transparent p-0 text-inherit;
|
||||||
|
}
|
||||||
|
|
||||||
.main-header {
|
.main-header {
|
||||||
@apply flex h-20 items-center justify-between bg-[var(--light-foreground)] p-4 shadow-sm;
|
/*
|
||||||
|
Should have higher z-value than the contents, but still less than the sidebar
|
||||||
|
(otherwise the navbar and the sidebar shadows will seem uneven).
|
||||||
|
*/
|
||||||
|
@apply z-5 flex h-20 items-center justify-between bg-[var(--light-foreground)] p-4 shadow-sm;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .main-header {
|
.dark .main-header {
|
||||||
|
@ -41,11 +41,7 @@
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
await handler(username, password)
|
await handler(username, password)
|
||||||
} catch (err) {
|
|
||||||
cError.set(err instanceof Error ? err.message : "Authentication failed")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte"
|
import { onDestroy, onMount } from "svelte"
|
||||||
import { goto } from "$app/navigation"
|
import { goto } from "$app/navigation"
|
||||||
import ThemeToggle from "./ThemeToggle.svelte"
|
import ThemeToggle from "./ThemeToggle.svelte"
|
||||||
import Sidebar from "./Sidebar.svelte"
|
import Sidebar from "./Sidebar.svelte"
|
||||||
@ -12,10 +12,13 @@
|
|||||||
availableNotes,
|
availableNotes,
|
||||||
versionHistory,
|
versionHistory,
|
||||||
cError,
|
cError,
|
||||||
type FullNote
|
type FullNote,
|
||||||
|
cSuccess
|
||||||
} from "$lib/client"
|
} from "$lib/client"
|
||||||
import ToggleSidebar from "$lib/icons/ToggleSidebar.svelte"
|
import ToggleSidebar from "$lib/icons/ToggleSidebar.svelte"
|
||||||
import VersionArrow from "$lib/icons/VersionArrow.svelte"
|
import VersionArrow from "$lib/icons/VersionArrow.svelte"
|
||||||
|
import Close from "$lib/icons/Close.svelte"
|
||||||
|
import { ERR_NOTIFICATION_DUR, SUC_NOTIFICATION_DUR } from "$lib/const"
|
||||||
|
|
||||||
// State
|
// State
|
||||||
let isComponentReady = false
|
let isComponentReady = false
|
||||||
@ -23,24 +26,21 @@
|
|||||||
let showSettings = false
|
let showSettings = false
|
||||||
let showVersionsDropdown = false
|
let showVersionsDropdown = false
|
||||||
let isEditing = false
|
let isEditing = false
|
||||||
|
let errorTimeout: ReturnType<typeof setTimeout> | null = null
|
||||||
|
let successTimeout: ReturnType<typeof setTimeout> | null = null
|
||||||
|
|
||||||
onMount(async (): Promise<any> => {
|
onMount(async (): Promise<any> => {
|
||||||
try {
|
|
||||||
// The following fetch attempts to refresh any expired tokens automatically
|
// The following fetch attempts to refresh any expired tokens automatically
|
||||||
await apiClient.getCurrentUser()
|
await apiClient.getCurrentUser()
|
||||||
|
|
||||||
// If still no current user after the fetch attempt, redirect to login
|
// If still no current user after the fetch attempt, redirect to login
|
||||||
if (!$currentUser) {
|
if (!$currentUser) {
|
||||||
console.log("no user data found, routing to auth page")
|
console.log("[VIEW] No user data found, routing to login page")
|
||||||
goto("/login")
|
goto("/login")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await loadNotes()
|
await loadNotes()
|
||||||
} catch (error) {
|
|
||||||
console.error(`error during auth: ${error}`)
|
|
||||||
goto("/login")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default to sidebar closed on mobile
|
// Default to sidebar closed on mobile
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
@ -165,7 +165,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deleteNote = async (noteID: string) => {
|
const deleteNote = async (noteID: string) => {
|
||||||
try {
|
|
||||||
await apiClient.deleteNote(noteID)
|
await apiClient.deleteNote(noteID)
|
||||||
|
|
||||||
// If we're deleting the currently active note, clear the current note
|
// If we're deleting the currently active note, clear the current note
|
||||||
@ -175,24 +174,79 @@
|
|||||||
|
|
||||||
// Refresh the notes list due to updates being pushed to server
|
// Refresh the notes list due to updates being pushed to server
|
||||||
await loadNotes()
|
await loadNotes()
|
||||||
} catch (err) {
|
|
||||||
cError.set(`Failed to delete note: ${err instanceof Error ? err.message : "Unknown error"}`)
|
cSuccess.set("Note deleted successfully.")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const logout = async () => {
|
const logout = async () => {
|
||||||
await apiClient.logout()
|
await apiClient.logout()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const errorUnsubscribe = cError.subscribe((value) => {
|
||||||
|
// Clear any existing timeout to avoid multiple timeouts
|
||||||
|
if (errorTimeout) {
|
||||||
|
clearTimeout(errorTimeout)
|
||||||
|
errorTimeout = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
errorTimeout = setTimeout(() => {
|
||||||
|
cError.set(null)
|
||||||
|
}, ERR_NOTIFICATION_DUR)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const successUnsubscribe = cSuccess.subscribe((value) => {
|
||||||
|
// Clear any existing timeout to avoid multiple timeouts
|
||||||
|
if (successTimeout) {
|
||||||
|
clearTimeout(successTimeout)
|
||||||
|
successTimeout = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
successTimeout = setTimeout(() => {
|
||||||
|
cSuccess.set(null)
|
||||||
|
}, SUC_NOTIFICATION_DUR)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
errorUnsubscribe()
|
||||||
|
successUnsubscribe()
|
||||||
|
|
||||||
|
// Clear any pending timeouts
|
||||||
|
if (errorTimeout) {
|
||||||
|
clearTimeout(errorTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (successTimeout) {
|
||||||
|
clearTimeout(successTimeout)
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if isComponentReady}
|
{#if isComponentReady}
|
||||||
<div class="main-layout-container">
|
<div class="main-layout-container">
|
||||||
<!-- Error notification -->
|
<!-- Error notification -->
|
||||||
{#if $cError}
|
{#if $cError}
|
||||||
<div class="main-error-popup">
|
<div class="main-info-popup">
|
||||||
<div class="error">
|
<div class="error">
|
||||||
{$cError}
|
{$cError}
|
||||||
<button class="ml-2 text-red-700" on:click={() => cError.set(null)}> × </button>
|
<button class="main-info-popup-button" on:click={() => cError.set(null)}>
|
||||||
|
<Close />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<!-- Success notification -->
|
||||||
|
{#if $cSuccess}
|
||||||
|
<div class="main-info-popup">
|
||||||
|
<div class="success">
|
||||||
|
{$cSuccess}
|
||||||
|
<button class="main-info-popup-button" on:click={() => cSuccess.set(null)}>
|
||||||
|
<Close />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { apiClient, cError } from "$lib/client"
|
import { apiClient, cSuccess } from "$lib/client"
|
||||||
import Close from "$lib/icons/Close.svelte"
|
import Close from "$lib/icons/Close.svelte"
|
||||||
import { isPasswordValid } from "$lib/utils"
|
import { isPasswordValid } from "$lib/utils"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
@ -10,11 +10,12 @@
|
|||||||
let currentPassword = ""
|
let currentPassword = ""
|
||||||
let newPassword = ""
|
let newPassword = ""
|
||||||
let confirmPassword = ""
|
let confirmPassword = ""
|
||||||
let passwordError = ""
|
let changePasswordModalError = ""
|
||||||
let isNewPasswordValid = true
|
let isNewPasswordValid = true
|
||||||
|
|
||||||
let deleteConfirmPassword = ""
|
let deleteConfirmPassword = ""
|
||||||
let deleteConfirmText = ""
|
let deleteConfirmText = ""
|
||||||
|
let deleteUserModalError = ""
|
||||||
|
|
||||||
let modalContent: HTMLDivElement | null = null
|
let modalContent: HTMLDivElement | null = null
|
||||||
let previouslyFocusedElement: HTMLElement | null = null
|
let previouslyFocusedElement: HTMLElement | null = null
|
||||||
@ -45,24 +46,24 @@
|
|||||||
|
|
||||||
const changePassword = async () => {
|
const changePassword = async () => {
|
||||||
if (!currentPassword) {
|
if (!currentPassword) {
|
||||||
passwordError = "Current password is required"
|
changePasswordModalError = "Current password is required."
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!newPassword) {
|
if (!newPassword) {
|
||||||
passwordError = "New password is required"
|
changePasswordModalError = "New password is required."
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newPassword !== confirmPassword) {
|
if (newPassword !== confirmPassword) {
|
||||||
passwordError = "New passwords do not match"
|
changePasswordModalError = "New passwords do not match."
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
;[isNewPasswordValid, passwordError] = isPasswordValid(newPassword)
|
;[isNewPasswordValid, changePasswordModalError] = isPasswordValid(newPassword)
|
||||||
|
|
||||||
if (!isNewPasswordValid) {
|
if (!isNewPasswordValid) {
|
||||||
// Error will be automatically displayed
|
// Error will be automatically displayed inside corresponding modal section
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,33 +73,32 @@
|
|||||||
newPassword = ""
|
newPassword = ""
|
||||||
confirmPassword = ""
|
confirmPassword = ""
|
||||||
|
|
||||||
cError.set("Password updated successfully")
|
cSuccess.set("Password updated successfully.")
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
cError.set(null)
|
|
||||||
onClose()
|
onClose()
|
||||||
}, 2000)
|
}, 100)
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
// Error handling is done by the API client
|
changePasswordModalError = err instanceof Error ? err.message : "Unknown error."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteAccount = async () => {
|
const deleteAccount = async () => {
|
||||||
if (!deleteConfirmPassword) {
|
if (!deleteConfirmPassword) {
|
||||||
passwordError = "Password is required to delete your account"
|
deleteUserModalError = "Password is required to delete your account."
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deleteConfirmText !== "DELETE") {
|
if (deleteConfirmText !== "DELETE") {
|
||||||
passwordError = "Please type DELETE to confirm account deletion"
|
deleteUserModalError = "Please type DELETE to confirm account deletion."
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await apiClient.deleteCurrentUser(deleteConfirmPassword)
|
await apiClient.deleteCurrentUser(deleteConfirmPassword)
|
||||||
// The API client will handle the redirect to login page after successful deletion
|
// The API client will handle the redirect to login page after successful deletion
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
// Error handling is done by the API client
|
deleteUserModalError = err instanceof Error ? err.message : "Unknown error."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,7 +142,7 @@
|
|||||||
role="document"
|
role="document"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<!-- Header -->
|
<!-- Header (title + close button) -->
|
||||||
<div class="modal-section flex items-center justify-between" role="heading" aria-level="1">
|
<div class="modal-section flex items-center justify-between" role="heading" aria-level="1">
|
||||||
<h2 class="text-xl font-bold">Settings</h2>
|
<h2 class="text-xl font-bold">Settings</h2>
|
||||||
<button on:click={onClose} class="modal-close-button" aria-label="Close settings">
|
<button on:click={onClose} class="modal-close-button" aria-label="Close settings">
|
||||||
@ -154,9 +154,9 @@
|
|||||||
<div class="modal-section">
|
<div class="modal-section">
|
||||||
<h3 class="mb-4 text-lg font-bold">Account Settings</h3>
|
<h3 class="mb-4 text-lg font-bold">Account Settings</h3>
|
||||||
|
|
||||||
{#if passwordError}
|
{#if changePasswordModalError}
|
||||||
<div class="error mb-4" role="alert">
|
<div class="error mb-4" role="alert">
|
||||||
{passwordError}
|
{changePasswordModalError}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@ -181,14 +181,22 @@
|
|||||||
<input type="password" id="confirmPassword" bind:value={confirmPassword} class="w-full" />
|
<input type="password" id="confirmPassword" bind:value={confirmPassword} class="w-full" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button on:click={changePassword} class="btn-primary w-full"> Change Password </button>
|
<button on:click={changePassword} class="btn-primary w-full rounded-full">
|
||||||
|
Change Password
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Danger zone -->
|
<!-- Danger zone -->
|
||||||
<div class="p-4">
|
<div class="modal-section">
|
||||||
<h3 class="mb-4 text-lg font-bold text-red-500">Danger Zone</h3>
|
<h3 class="mb-4 text-lg font-bold text-red-500">Danger Zone</h3>
|
||||||
|
|
||||||
|
{#if deleteUserModalError}
|
||||||
|
<div class="error mb-4" role="alert">
|
||||||
|
{deleteUserModalError}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<p class="text-sm">
|
<p class="text-sm">
|
||||||
Deleting your account will permanently remove all your notes and account information. This
|
Deleting your account will permanently remove all your notes and account information. This
|
||||||
@ -220,7 +228,7 @@
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
on:click={deleteAccount}
|
on:click={deleteAccount}
|
||||||
class="w-full rounded bg-red-500 px-4 py-2 font-bold text-white hover:bg-red-600 disabled:cursor-not-allowed disabled:opacity-50"
|
class="w-full rounded-full bg-red-500 font-bold text-white hover:bg-red-600 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
disabled={deleteConfirmText !== "DELETE" || !deleteConfirmPassword}
|
disabled={deleteConfirmText !== "DELETE" || !deleteConfirmPassword}
|
||||||
>
|
>
|
||||||
Delete My Account
|
Delete My Account
|
||||||
|
@ -90,7 +90,7 @@
|
|||||||
<div class="relative pl-4">
|
<div class="relative pl-4">
|
||||||
<button
|
<button
|
||||||
on:click={closeSidebar}
|
on:click={closeSidebar}
|
||||||
class="btn-secondary rounded-full p-2 md:hidden"
|
class="btn-secondary h-9 w-9 rounded-full p-2 md:hidden"
|
||||||
aria-label="Close sidebar"
|
aria-label="Close sidebar"
|
||||||
>
|
>
|
||||||
<Close />
|
<Close />
|
||||||
|
@ -30,3 +30,7 @@ export const VIEW_COOKIE_PATH = import.meta.env.VITE_VIEW_COOKIE_PATH || "/"
|
|||||||
export const VIEW_COOKIE_DOMAIN = import.meta.env.VITE_VIEW_COOKIE_DOMAIN || "localhost"
|
export const VIEW_COOKIE_DOMAIN = import.meta.env.VITE_VIEW_COOKIE_DOMAIN || "localhost"
|
||||||
export const COOKIE_SAME_SITE = import.meta.env.VITE_COOKIE_SAME_SITE || "strict"
|
export const COOKIE_SAME_SITE = import.meta.env.VITE_COOKIE_SAME_SITE || "strict"
|
||||||
export const COOKIE_SECURE = import.meta.env.PROD ? true : false
|
export const COOKIE_SECURE = import.meta.env.PROD ? true : false
|
||||||
|
|
||||||
|
// Error/success notification display durations
|
||||||
|
export const ERR_NOTIFICATION_DUR = 8 * 1000 // 8 s.
|
||||||
|
export const SUC_NOTIFICATION_DUR = 8 * 1000 // 8 s.
|
||||||
|
@ -1,9 +1,3 @@
|
|||||||
<svg
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="h-6 w-6"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 223 B After Width: | Height: | Size: 201 B |
@ -3,7 +3,7 @@
|
|||||||
import { apiClient } from "$lib/client"
|
import { apiClient } from "$lib/client"
|
||||||
|
|
||||||
const loginHandler = (username: string, password: string) => {
|
const loginHandler = (username: string, password: string) => {
|
||||||
return apiClient.login(username, password)
|
return apiClient.login(username, password) as Promise<void>
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user