2022-07-07 05:19:05 +08:00
|
|
|
<script>
|
|
|
|
import { createEventDispatcher } from "svelte";
|
|
|
|
import { slide } from "svelte/transition";
|
|
|
|
import CommonHelper from "@/utils/CommonHelper";
|
|
|
|
import ApiClient from "@/utils/ApiClient";
|
2022-10-30 16:28:14 +08:00
|
|
|
import tooltip from "@/actions/tooltip";
|
2022-07-07 05:19:05 +08:00
|
|
|
import { setErrors } from "@/stores/errors";
|
|
|
|
import { confirm } from "@/stores/confirmation";
|
|
|
|
import { addSuccessToast } from "@/stores/toasts";
|
|
|
|
import Field from "@/components/base/Field.svelte";
|
|
|
|
import Toggler from "@/components/base/Toggler.svelte";
|
|
|
|
import OverlayPanel from "@/components/base/OverlayPanel.svelte";
|
|
|
|
|
|
|
|
const dispatch = createEventDispatcher();
|
|
|
|
const formId = "admin_" + CommonHelper.randomString(5);
|
|
|
|
|
|
|
|
let panel;
|
2023-08-15 02:20:49 +08:00
|
|
|
let admin = {};
|
2022-07-07 05:19:05 +08:00
|
|
|
let isSaving = false;
|
|
|
|
let confirmClose = false; // prevent close recursion
|
|
|
|
let avatar = 0;
|
|
|
|
let email = "";
|
|
|
|
let password = "";
|
|
|
|
let passwordConfirm = "";
|
|
|
|
let changePasswordToggle = false;
|
|
|
|
|
2023-08-22 18:01:08 +08:00
|
|
|
$: isNew = !admin?.id;
|
2023-08-15 02:20:49 +08:00
|
|
|
|
2022-07-07 05:19:05 +08:00
|
|
|
$: hasChanges =
|
2023-08-15 02:20:49 +08:00
|
|
|
(isNew && email != "") || changePasswordToggle || email !== admin.email || avatar !== admin.avatar;
|
2022-07-07 05:19:05 +08:00
|
|
|
|
|
|
|
export function show(model) {
|
|
|
|
load(model);
|
|
|
|
|
|
|
|
confirmClose = true;
|
|
|
|
|
|
|
|
return panel?.show();
|
|
|
|
}
|
|
|
|
|
|
|
|
export function hide() {
|
|
|
|
return panel?.hide();
|
|
|
|
}
|
|
|
|
|
|
|
|
function load(model) {
|
2023-08-15 02:20:49 +08:00
|
|
|
admin = structuredClone(model || {});
|
2022-07-07 05:19:05 +08:00
|
|
|
reset(); // reset form
|
|
|
|
}
|
|
|
|
|
|
|
|
function reset() {
|
|
|
|
changePasswordToggle = false;
|
|
|
|
email = admin?.email || "";
|
|
|
|
avatar = admin?.avatar || 0;
|
|
|
|
password = "";
|
|
|
|
passwordConfirm = "";
|
2022-10-30 16:28:14 +08:00
|
|
|
setErrors({}); // reset errors
|
2022-07-07 05:19:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
function save() {
|
|
|
|
if (isSaving || !hasChanges) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
isSaving = true;
|
|
|
|
|
|
|
|
const data = { email, avatar };
|
2023-08-15 02:20:49 +08:00
|
|
|
if (isNew || changePasswordToggle) {
|
2022-07-07 05:19:05 +08:00
|
|
|
data["password"] = password;
|
|
|
|
data["passwordConfirm"] = passwordConfirm;
|
|
|
|
}
|
|
|
|
|
|
|
|
let request;
|
2023-08-15 02:20:49 +08:00
|
|
|
if (isNew) {
|
2022-08-02 22:00:14 +08:00
|
|
|
request = ApiClient.admins.create(data);
|
2022-07-07 05:19:05 +08:00
|
|
|
} else {
|
2022-08-02 22:00:14 +08:00
|
|
|
request = ApiClient.admins.update(admin.id, data);
|
2022-07-07 05:19:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
request
|
|
|
|
.then(async (result) => {
|
|
|
|
confirmClose = false;
|
|
|
|
hide();
|
2023-08-15 02:20:49 +08:00
|
|
|
addSuccessToast(isNew ? "Successfully created admin." : "Successfully updated admin.");
|
2022-07-07 05:19:05 +08:00
|
|
|
|
2022-08-02 22:00:14 +08:00
|
|
|
if (ApiClient.authStore.model?.id === result.id) {
|
|
|
|
ApiClient.authStore.save(ApiClient.authStore.token, result);
|
2022-07-07 05:19:05 +08:00
|
|
|
}
|
2023-05-14 03:10:14 +08:00
|
|
|
|
|
|
|
dispatch("save", result);
|
2022-07-07 05:19:05 +08:00
|
|
|
})
|
|
|
|
.catch((err) => {
|
2023-05-14 03:10:14 +08:00
|
|
|
ApiClient.error(err);
|
2022-07-07 05:19:05 +08:00
|
|
|
})
|
|
|
|
.finally(() => {
|
|
|
|
isSaving = false;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function deleteConfirm() {
|
|
|
|
if (!admin?.id) {
|
|
|
|
return; // nothing to delete
|
|
|
|
}
|
|
|
|
|
|
|
|
confirm(`Do you really want to delete the selected admin?`, () => {
|
2022-08-06 23:15:18 +08:00
|
|
|
return ApiClient.admins
|
|
|
|
.delete(admin.id)
|
2022-07-07 05:19:05 +08:00
|
|
|
.then(() => {
|
|
|
|
confirmClose = false;
|
|
|
|
hide();
|
|
|
|
addSuccessToast("Successfully deleted admin.");
|
|
|
|
dispatch("delete", admin);
|
|
|
|
})
|
|
|
|
.catch((err) => {
|
2023-05-14 03:10:14 +08:00
|
|
|
ApiClient.error(err);
|
2022-07-07 05:19:05 +08:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<OverlayPanel
|
|
|
|
bind:this={panel}
|
|
|
|
popup
|
|
|
|
class="admin-panel"
|
|
|
|
beforeHide={() => {
|
|
|
|
if (hasChanges && confirmClose) {
|
|
|
|
confirm("You have unsaved changes. Do you really want to close the panel?", () => {
|
|
|
|
confirmClose = false;
|
|
|
|
hide();
|
|
|
|
});
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}}
|
|
|
|
on:hide
|
|
|
|
on:show
|
|
|
|
>
|
|
|
|
<svelte:fragment slot="header">
|
|
|
|
<h4>
|
2023-08-15 02:20:49 +08:00
|
|
|
{isNew ? "New admin" : "Edit admin"}
|
2022-07-07 05:19:05 +08:00
|
|
|
</h4>
|
|
|
|
</svelte:fragment>
|
|
|
|
|
|
|
|
<form id={formId} class="grid" autocomplete="off" on:submit|preventDefault={save}>
|
2023-08-15 02:20:49 +08:00
|
|
|
{#if !isNew}
|
2023-02-19 01:33:42 +08:00
|
|
|
<Field class="form-field readonly" name="id" let:uniqueId>
|
2022-07-07 05:19:05 +08:00
|
|
|
<label for={uniqueId}>
|
|
|
|
<i class={CommonHelper.getFieldTypeIcon("primary")} />
|
2023-02-19 01:33:42 +08:00
|
|
|
<span class="txt">id</span>
|
2022-07-07 05:19:05 +08:00
|
|
|
</label>
|
2022-10-30 16:28:14 +08:00
|
|
|
<div class="form-field-addon">
|
|
|
|
<i
|
|
|
|
class="ri-calendar-event-line txt-disabled"
|
|
|
|
use:tooltip={{
|
|
|
|
text: `Created: ${admin.created}\nUpdated: ${admin.updated}`,
|
|
|
|
position: "left",
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</div>
|
2023-02-19 01:33:42 +08:00
|
|
|
<input type="text" id={uniqueId} value={admin.id} readonly />
|
2022-07-07 05:19:05 +08:00
|
|
|
</Field>
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
<div class="content">
|
|
|
|
<p class="section-title">Avatar</p>
|
|
|
|
<div class="flex flex-gap-xs flex-wrap">
|
|
|
|
{#each [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] as index}
|
2022-10-30 16:28:14 +08:00
|
|
|
<button
|
|
|
|
type="button"
|
2022-07-07 05:19:05 +08:00
|
|
|
class="link-fade thumb thumb-circle {index == avatar ? 'thumb-active' : 'thumb-sm'}"
|
|
|
|
on:click={() => (avatar = index)}
|
|
|
|
>
|
|
|
|
<img
|
|
|
|
src="{import.meta.env.BASE_URL}images/avatars/avatar{index}.svg"
|
|
|
|
alt="Avatar {index}"
|
|
|
|
/>
|
2022-10-30 16:28:14 +08:00
|
|
|
</button>
|
2022-07-07 05:19:05 +08:00
|
|
|
{/each}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<Field class="form-field required" name="email" let:uniqueId>
|
|
|
|
<label for={uniqueId}>
|
|
|
|
<i class={CommonHelper.getFieldTypeIcon("email")} />
|
|
|
|
<span class="txt">Email</span>
|
|
|
|
</label>
|
|
|
|
<input type="email" autocomplete="off" id={uniqueId} required bind:value={email} />
|
|
|
|
</Field>
|
|
|
|
|
2023-08-15 02:20:49 +08:00
|
|
|
{#if !isNew}
|
2022-07-07 05:19:05 +08:00
|
|
|
<Field class="form-field form-field-toggle" let:uniqueId>
|
|
|
|
<input type="checkbox" id={uniqueId} bind:checked={changePasswordToggle} />
|
|
|
|
<label for={uniqueId}>Change password</label>
|
|
|
|
</Field>
|
|
|
|
{/if}
|
|
|
|
|
2023-08-15 02:20:49 +08:00
|
|
|
{#if isNew || changePasswordToggle}
|
2022-07-07 05:19:05 +08:00
|
|
|
<div class="col-12">
|
2022-08-06 23:15:18 +08:00
|
|
|
<div class="grid" transition:slide|local={{ duration: 150 }}>
|
2022-07-07 05:19:05 +08:00
|
|
|
<div class="col-sm-6">
|
|
|
|
<Field class="form-field required" name="password" let:uniqueId>
|
|
|
|
<label for={uniqueId}>
|
|
|
|
<i class="ri-lock-line" />
|
|
|
|
<span class="txt">Password</span>
|
|
|
|
</label>
|
|
|
|
<input
|
|
|
|
type="password"
|
|
|
|
autocomplete="new-password"
|
|
|
|
id={uniqueId}
|
|
|
|
required
|
|
|
|
bind:value={password}
|
|
|
|
/>
|
|
|
|
</Field>
|
|
|
|
</div>
|
|
|
|
<div class="col-sm-6">
|
|
|
|
<Field class="form-field required" name="passwordConfirm" let:uniqueId>
|
|
|
|
<label for={uniqueId}>
|
|
|
|
<i class="ri-lock-line" />
|
|
|
|
<span class="txt">Password confirm</span>
|
|
|
|
</label>
|
|
|
|
<input
|
|
|
|
type="password"
|
|
|
|
autocomplete="new-password"
|
|
|
|
id={uniqueId}
|
|
|
|
required
|
|
|
|
bind:value={passwordConfirm}
|
|
|
|
/>
|
|
|
|
</Field>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{/if}
|
|
|
|
</form>
|
|
|
|
|
|
|
|
<svelte:fragment slot="footer">
|
2023-08-15 02:20:49 +08:00
|
|
|
{#if !isNew}
|
2023-01-29 02:13:15 +08:00
|
|
|
<button type="button" aria-label="More" class="btn btn-sm btn-circle btn-transparent">
|
2022-07-07 05:19:05 +08:00
|
|
|
<!-- empty span for alignment -->
|
|
|
|
<span />
|
|
|
|
<i class="ri-more-line" />
|
|
|
|
<Toggler class="dropdown dropdown-upside dropdown-left dropdown-nowrap">
|
2022-10-30 16:28:14 +08:00
|
|
|
<button type="button" class="dropdown-item txt-danger" on:click={() => deleteConfirm()}>
|
2022-07-07 05:19:05 +08:00
|
|
|
<i class="ri-delete-bin-7-line" />
|
|
|
|
<span class="txt">Delete</span>
|
|
|
|
</button>
|
|
|
|
</Toggler>
|
|
|
|
</button>
|
|
|
|
<div class="flex-fill" />
|
|
|
|
{/if}
|
|
|
|
|
2023-01-24 03:57:35 +08:00
|
|
|
<button type="button" class="btn btn-transparent" disabled={isSaving} on:click={() => hide()}>
|
2022-07-07 05:19:05 +08:00
|
|
|
<span class="txt">Cancel</span>
|
|
|
|
</button>
|
|
|
|
<button
|
|
|
|
type="submit"
|
|
|
|
form={formId}
|
|
|
|
class="btn btn-expanded"
|
|
|
|
class:btn-loading={isSaving}
|
|
|
|
disabled={!hasChanges || isSaving}
|
|
|
|
>
|
2023-08-15 02:20:49 +08:00
|
|
|
<span class="txt">{isNew ? "Create" : "Save changes"}</span>
|
2022-07-07 05:19:05 +08:00
|
|
|
</button>
|
|
|
|
</svelte:fragment>
|
|
|
|
</OverlayPanel>
|