@lucidcms/admin
Version:
The admin SolidJS SPA for Lucid CMS.
1,256 lines (1,243 loc) • 733 kB
JavaScript
import { delegateEvents, createComponent, template, insert, setAttribute, memo, effect, className, style, mergeProps, spread, use } from 'solid-js/web';
import { QueryClient, QueryClientProvider, createQuery, useQueryClient, createMutation } from '@tanstack/solid-query';
import toast2, { Toaster, toast } from 'solid-toast';
import i18next from 'i18next';
import { createSignal, createMemo, createEffect, Switch, Match, Show, on, For, Index, onMount, onCleanup, batch } from 'solid-js';
import classNames14 from 'classnames';
import { FaSolidCode, FaSolidPlus, FaSolidTrash, FaSolidCheck, FaSolidExclamation, FaSolidTriangleExclamation, FaSolidInfo, FaSolidSatelliteDish, FaSolidCalendar, FaSolidXmark, FaSolidCircle, FaSolidFloppyDisk, FaSolidCalendarPlus, FaSolidClock, FaSolidLanguage, FaSolidLayerGroup, FaSolidIdCard, FaSolidT, FaSolidUserTie, FaSolidEnvelope, FaSolidPaperPlane, FaSolidBox, FaSolidPhotoFilm, FaSolidUsers, FaSolidGear, FaSolidUserLock, FaSolidSort, FaSolidBoxesStacked, FaSolidUser, FaSolidFilter, FaSolidDatabase, FaSolidGlobe, FaSolidCaretRight, FaSolidShield, FaSolidCircleChevronUp, FaSolidGripLines, FaSolidMagnifyingGlass, FaSolidChevronLeft, FaSolidChevronRight, FaSolidArrowRight, FaSolidEyeSlash, FaSolidEye, FaSolidEllipsisVertical, FaSolidHouse, FaSolidRightFromBracket, FaSolidFile, FaSolidCaretUp, FaSolidMinus, FaSolidTable, FaSolidTrashCan, FaSolidFileZipper, FaSolidFileAudio, FaSolidFileVideo, FaSolidFileLines, FaSolidCopy, FaSolidArrowUpFromBracket, FaSolidArrowRotateLeft, FaSolidPen } from 'solid-icons/fa';
import { Router, Route, useNavigate, useLocation, useParams, A } from '@solidjs/router';
import equal from 'fast-deep-equal/es6';
import { createStore, produce } from 'solid-js/store';
import LogoIcon from '../assets/svgs/logo-icon.svg';
import { Tooltip, DropdownMenu, HoverCard, Dialog, Checkbox, AlertDialog, Pagination as Pagination$1, Image as Image$1, Alert } from '@kobalte/core';
import notifySvg from '../assets/illustrations/notify.svg';
import noPermission from '../assets/illustrations/no-permission.svg';
import { debounce } from '@solid-primitives/scheduled';
import Quill from 'quill';
import 'quill/dist/quill.snow.css';
import { nanoid } from 'nanoid';
import { encode } from 'blurhash';
import { FastAverageColor } from 'fast-average-color';
import brickPlaceholder from '../assets/images/brick-placeholder.jpg';
import brickIcon from '../assets/svgs/default-brick-icon-white.svg';
import 'solid-devtools';
// src/App.tsx
// src/translations/en.json
var en_default = {
logout: "Logout",
home: "Home",
environment: "Environment",
media: "Media",
users: "Users",
close: "Close",
settings: "Settings",
multi_builder_collections: "Multiple Builder Collections",
single_builder_collections: "Single Builder Collection",
create_environment: "Create Environment",
details: "Details",
title: "Title",
back: "Back",
documents: "Documents",
permissions: "Permissions",
key: "Key",
delivered: "Delivered",
translations: "Translations",
enabled: "Enabled",
unsaved: "Unsaved",
confirm_delete: "Confirm Delete",
standard: "Standard",
restore_revision: "Restore Revision",
bricks: "Bricks",
document_not_published: "This document is not published",
assign_bricks_description: "Select the bricks you wish this environment to have access to.",
collections: "Collections",
assign_collections_description: "Assign the collection you wish the environment to have access to.",
forms: "Forms",
remove_entry: "Remove entry",
failed_count: "Failed Count",
last_success_at: "Last Success At",
change_order: "Change Order",
details_lang: "Details ({{code}})",
select: "Select",
select_document: "Select Document",
back_to_document: "Back to document",
publish: "Publish",
updated_by: "Updated By",
toggle_panel: "Toggle panel",
assign_forms_description: "Select the forms the environment should have access to.",
update: "Update",
no_revisions_found: "No revisions found",
no_revisions_found_message: "No revisions have been created for this document yet. Whenever you save or publish a document, a new revision will be created.",
revision_history: "Revision History",
revision: "Revision",
save: "Save",
selected_document: "Selected Document: {{id}}",
toggle_details: "Toggle details",
add_brick: "Add Brick",
create: "Create",
content: "Content",
revisions: "Revisions",
error_title: "Something went wrong",
collection: "Collection",
error_message: "An error has occurred. Please try again later.",
no_results: "No results found",
no_results_message: "There are no results to show. Try adjusting your filters to find what you're looking for.",
please_reset_password_message: "Please reset your password before continuing.",
password_reset_required: "Password Reset Required",
confirm: "Confirm",
no_entries: "No entries",
cancel: "Cancel",
sort: "Sort",
per_page: "Per page {{count}}",
filter: "Filter",
active: "Active",
no_translation: "No translation",
inactive: "Inactive",
select_media: "Select {{type}}",
select_media_title: "Select Media",
select_new_media: "Select new {{type}}",
remove_media: "Remove {{type}}",
username: "Username",
password: "Password",
confirm_password: "Confirm Password",
email: "Email",
url: "URL",
select_link: "Select Link",
set_link: "Set Link",
either_title_or_slug_dont_have_translations: "Either the title or slug have missing translations",
this_is_the_homepage: "This is the homepage",
roles: "Roles",
update_brick: "Update {{name}}",
add_entry: "Add Entry",
reset_password: "Reset Password",
back_to_login: "Back to Login",
forgot_password: "Forgot Password?",
pagination_text: "Page {{page}} of {{lastPage}} - {{total}} total results",
pagination_previous: "Previous",
pagination_next: "Next",
send_password_reset: "Send Password Reset Link",
token_provided_invalid: "The token you provided is invalid.",
token_provided_invalid_description: "The token you provided is invalid or expired. Please ensure you have copied the link correctly, or request a new password reset link.",
locked_collection_message: "This collection is locked. This means documents can only be viewed, not edited or created.",
first_name: "First Name",
last_name: "Last Name",
created_at: "Created At",
updated_at: "Updated At",
description: "Description",
last_attempt_at: "Last Attempt At",
role: "Role",
error: "Error",
unknown_error_message: "An unknown error has occurred. Please try again later.",
empty_body_error_message: "The request body was empty.",
edit: "Edit",
delete: "Delete",
toggle_col_visibility: "Toggle Column Visibility",
show_options: "Show Options",
loading: "Loading",
ellipsis: "...",
reset_filters: "Reset Filters",
selected: "Selected",
reset: "Reset",
item: "item",
items: "items",
remove: "Remove",
current_password: "Current Password",
new_password: "New Password",
update_password: "Update Your Password",
remove_all: "Remove All",
created_by: "Created By",
collection_key: "Collection Key",
name: "Name",
file_extension: "File Extension",
username_or_email: "Username or Email",
file_size: "File Size",
width: "Width",
true: "True",
false: "False",
height: "Height",
mime_type: "Mime Type",
users_permissions: "User Permissions",
regenerate_api_key: "Regenerate API Key",
roles_permissions: "Role Permissions",
media_permissions: "Media Permissions",
environment_permissions: "Environment Permissions",
emails_permissions: "Email Permissions",
content_permissions: "Content Permissions",
category_permissions: "Category Permissions",
permissions_create_user: "Create Users",
permissions_update_user: "Update Users",
permissions_delete_user: "Delete Users",
permissions_create_role: "Create Roles",
permissions_update_role: "Update Roles",
permissions_delete_role: "Delete Roles",
permissions_create_media: "Create Media",
permissions_update_media: "Update Media",
permissions_delete_media: "Delete Media",
permissions_read_email: "Read Emails",
permissions_send_email: "Send Emails",
permissions_delete_email: "Delete Emails",
permissions_create_content: "Create Documents",
permissions_update_content: "Update Documents",
permissions_delete_content: "Delete Documents",
permissions_restore_content: "Restore Revisions",
permissions_publish_content: "Publish Documents",
permissions_create_category: "Create Categories",
permissions_update_category: "Update Categories",
permissions_delete_category: "Delete Categories",
permissions_create_client_integration: "Create Client Integrations",
permissions_update_client_integration: "Update Client Integrations",
permissions_delete_client_integration: "Delete Client Integrations",
permissions_regenerate_client_integration: "Regenerate API Keys",
global_permissions: "Global Permissions",
is_super_admin: "Is super admin",
documentation: "Documentation",
password_description: "Ensure your password is at least 8 characters long.",
no_permission: "You do not have permission.",
no_permission_description: "Please contact your administrator if you believe this is an error.",
pagination_empty: "No entries found",
type: "Type",
image: "Image",
video: "Video",
builder_area: "Builder",
audio: "Audio",
document: "Document",
archive: "Archive",
unknown: "Unknown",
no_options_available: "No options available",
super_admin: "Super Admin",
yes: "Yes",
no: "No",
password_confirmation: "Password Confirmation",
general: "General",
integrations: "Integrations",
processed_images: "Processed Images",
processed_images_setting_message: "Each image can have a maximum of {{limit}} processed images. These count towards your storage limit",
clear_all: "Clear all",
clear_all_processed_images_setting_message: "After clearing all your processed images, it's recommended to review each page of your client(s) to reprocess the images. The first load will take longer since that's when processing occurs.",
clear_all_processed_images_button: "Clear all {{count}} images",
storage_breakdown: "Storage Breakdown",
storage_breakdown_setting_message: "See your used and remaining storage",
storage_remaining_title: "{{storage}} remaining",
drag_and_drop_file_or: "Drag & drop your file/image or",
upload_here: "upload here",
if_left_blank_file_will_be_removed: "If left blank, the current file will be removed",
back_to_current_file: "Back to current file",
preview: "Preview",
choose_file: "Choose file",
full_slug: "Full Slug",
label: "Label",
page_link_label_tooltip: "If left blank, the page title will be used",
alt: "Alt",
meta: "Meta",
open_in_new_tab: "Open in new tab",
dimensions: "Dimensions",
extension: "Extension",
clear_processed: "Clear processed",
sent_count: "Sent Count",
internal: "Internal",
external: "External",
sent: "Sent",
failed: "Failed",
pending: "Pending",
status: "Status",
template: "Template",
subject: "Subject",
to: "To",
constructing_your_page: "Constructing your page",
get_started_by_adding_bricks: "Get started by adding bricks to your page. Manage the content and layout of these to determine the structure of your page.",
emails: "Emails",
first_attempt: "First Attempt",
last_attempt: "Last Attempt",
from: "From",
data: "Data",
resend: "Resend",
template_data: "Template Data",
slug: "Slug",
category: "Category",
published: "Published",
draft: "Draft",
create_dynamic: "Create {{name}}",
is_homepage: "Is homepage",
parent_page: "Parent Page",
no_option_selected: "No option selected",
search: "Search",
no_results_found: "No results found",
clear: "Clear",
none: "None",
nothing_selected: "Nothing selected",
categories: "Categories",
is_homepage_description: "There can only be one homepage per environment. If you select this page as the homepage, the current homepage will be removed. Homepages cannot be set as parent pages, or have their own parent page.",
page_slug_description: "A good slug should be short, descriptive and contain keywords relevant to the page. It should be lowercase and contain hyphens instead of spaces. Any special characters including a slash will be removed.",
quick_edit: "Quick Edit",
excerpt: "Excerpt",
author: "Author",
select_media_description: "Click the media you wish to use.",
select_document_title: "Select Document",
select_document_description: "Click the document you wish to use.",
search_by_username: "Search by username",
no_users: "No Users Found",
no_users_description: "We cannot find any users. You can create a new user by clicking the button below.",
create_user: "Create User",
no_entries_title: "No entries",
no_entries_description: "There are no entries. You can create a new entry by clicking the button below.",
create_entry: "Create Entry",
no_roles: "No Roles Found",
no_roles_description: "We cannot find any roles. You can create a new role by clicking the button below.",
create_role: "Create Role",
no_media: "No Media Found",
no_media_description: "We cannot find any media. You can upload new media by clicking the button below.",
upload_media: "Upload Media",
no_collections: "No Collections Found",
no_collections_description: "We cannot find any collections. To create one, use the CollectionBuilder and import the instance in the lucid.config file.",
no_documents: "No {{collectionMultiple}} Found",
no_documents_description: "We cannot find any {{collectionMultiple}}. You can create a new {{collectionSingle}} by clicking the button below.",
no_documents_description_doc_select: "We cannot find any {{collectionMultiple}}. You can create new a new {{collectionSingle}} navigating to the collection page.",
create_document: "Create {{collectionSingle}}",
delete_environment_modal_title: "Delete Environment",
delete_environment_modal_description: "Are you sure you want to delete this environment?",
delete_role_modal_title: "Delete Role",
delete_role_modal_description: "Are you sure you want to delete this role?",
delete_user_modal_title: "Delete User",
delete_user_modal_description: "Are you sure you want to delete this user?",
clear_all_processed_images_modal_title: "Clear all processed images",
clear_all_processed_images_modal_description: "Are you sure you want to clear all processed images?",
delete_media_modal_title: "Delete Media",
delete_media_modal_description: "Are you sure you want to delete this media?",
clear_processed_images_modal_title: "Clear processed images",
clear_processed_images_modal_description: "Are you sure you want to clear the processed images for this media?",
delete_email_modal_title: "Delete Email",
delete_email_modal_description: "Are you sure you want to delete this email?",
resend_email_modal_title: "Resend Email",
resend_email_modal_description: "Are you sure you want to resend this email?",
delete_document_modal_title: "Delete {{name}}",
delete_document_modal_description: "Are you sure you want to delete this {{name}}? This will delete the draft/published versions as well as the revisions.",
publish_document_modal_title: "Publish {{name}}",
publish_document_modal_description: "Are you sure you want to publish this {{name}}?",
navigation_guard_modal_title: "Unsaved changes will be lost",
navigation_guard_modal_description: "Are you sure you want to leave this page? Any unsaved changes will be lost.",
user_password_reset_modal_title: "Reset User Password",
user_password_reset_modal_description: "Confirming the below will require the user to reset their password on next login or when they next use Lucid.",
delete_client_integration_modal_title: "Delete Client Integration",
delete_client_integration_modal_description: "Are you sure you want to delete this client integration?",
copy_api_key_modal_title: "Copy API Key",
copy_api_key_modal_description: "Copy the API key and keep it safe. You will not be able to see it again.",
regenerate_api_key_modal_title: "Regenerate API Key",
regenerate_api_key_modal_description: "Are you sure you want to regenerate the API key for this client integration?",
promote_to_draft_modal_title: "Promote to Draft",
promote_to_draft_modal_description: "We notice drafts are enabled for this collection, but there is only a published version for this document. To continue editing, a draft version will be created from the published version.",
update_role_panel_title: "Edit {{name}} Role",
update_role_panel_description: "Update the permissions for the {{name}} role.",
create_role_panel_title: "Create a New Role",
create_role_panel_description: "Configure the name and permissions for the new role.",
update_user_panel_title: "Update User",
update_user_panel_description: "Update the details for the user.",
create_user_panel_title: "Create a New User",
create_user_panel_description: "New users will receive an email with a link to set their password. If this isn't received, you can resend the email or get the link from viewing the email from the emails page.",
update_media_panel_title: "Update Media",
update_media_panel_description: "Update the media entry.",
create_media_panel_title: "Create Media",
create_media_panel_description: "Create a new media entry.",
preview_email_panel_title: "Preview Email",
create_page_panel_title: "Create {{name}}",
create_page_panel_description: "Create a new page for the {{collection}} collection.",
update_page_panel_title: "Update {{name}}",
update_page_panel_description: "Update the {{collection}} collection page.",
create_client_integration_panel_title: "Create Client Integration",
create_client_integration_panel_description: "Create a new client integration.",
update_client_integration_panel_title: "Update Client Integration",
update_client_integration_panel_description: "Update your client integration details",
restore_revision_toast_title: "{{name}} restored",
restore_revision_toast_message: "The {{name}} has been restored",
password_reset_toast_title: "Password reset email sent",
password_reset_toast_message: "Please check your email for a password reset link",
promote_version_toast_title: "{{name}} promoted",
promote_version_toast_message: "The {{name}} has been promoted to {{versionType}}",
login_success_toast_title: "Login successful",
login_success_toast_message: "You are now logged in",
logout_success_toast_title: "Logout successful",
logout_success_toast_message: "You are now logged out",
password_reset_success_toast_title: "Password reset successful",
password_reset_success_toast_message: "You can now login with your new password",
setup_success_toast_title: "Welcome aboard!",
setup_success_toast_message: "Your admin account has been created successfully",
create_initial_admin: "Create Admin Account",
setup_route_title: "Welcome to Lucid CMS",
setup_route_description: "Let's get you started by creating your admin account",
environment_created_toast_title: "Environment created",
environment_created_toast_message: "The environment has been created",
environment_deleted_toast_title: "Environment deleted",
environment_deleted_toast_message: "The environment has been deleted",
environment_updated_toast_title: "Environment updated",
environment_updated_toast_message: "The environment has been updated",
role_created_toast_title: "Role created",
role_created_toast_message: "The role has been created",
role_update_toast_title: "Role updated",
role_update_toast_message: "{{name}} has been updated",
role_deleted_toast_title: "Role deleted",
role_deleted_toast_message: "The role has been deleted",
integration_created_toast_title: "Integration created",
integration_created_toast_message: "The integration has been created",
user_update_toast_title: "User updated",
user_update_toast_message: "User has been updated",
user_create_toast_title: "User created",
user_create_toast_message: "User has been created",
user_deleted_toast_title: "User deleted",
user_deleted_toast_message: "The user has been deleted",
no_permission_toast_title: "No permission",
no_permission_toast_message: "Your don't have permission to perform this action. Contact an admin if you think there is a mistake",
copy_to_clipboard_toast_title: "Copied to clipboard",
delete_processed_images_toast_title: "Processed images deleted",
delete_processed_images_toast_message: "Processed images have been deleted",
media_update_toast_title: "Media updated",
media_update_toast_message: "Media has been updated",
media_create_toast_title: "Media created",
media_create_toast_message: "Media has been created",
media_deleted_toast_title: "Media deleted",
media_deleted_toast_message: "The media has been deleted",
email_deleted_toast_title: "Email deleted",
email_deleted_toast_message: "The email has been deleted",
email_resent_toast_title: "Email resent",
email_resent_toast_message: "The email has been resent",
email_resent_toast_error_title: "Email resend error",
metadata: "Metadata",
email_resent_toast_error_message: "There was an error resending the email",
account_update_toast_title: "Account updated",
account_update_toast_message: "Your account has been updated",
regenerate_api_key_toast_title: "API Key Regenerated",
regenerate_api_key_toast_message: "The API key has been regenerated",
client_integration_update_toast_title: "Client Integration Updated",
client_integration_update_toast_message: "The client integration has been updated",
last_updated_by: "Last Updated By",
last_updated_at: "Last Updated At",
document_id: "Document ID",
available_builder_bricks: "Available Builder Bricks",
fixed_bricks: "Fixed Bricks",
total_bricks: "Total Bricks",
clear_filters: "Clear Filters",
create_toast_title: "{{name}} created",
create_toast_message: "The {{name}} has been created",
deleted_toast_title: "{{name}} deleted",
deleted_toast_message: "The {{name}} has been deleted",
update_toast_title: "{{name}} updated",
update_toast_message: "The {{name}} has been updated",
publish_toast_title: "{{name}} published",
publish_toast_message: "The {{name}} has been published",
account_route_title: "Account",
account_route_description: "Manage your account details and settings.",
roles_route_title: "Roles",
roles_route_description: "Create and manage roles.",
forgot_password_route_title: "Forgot your password?",
forgot_password_route_description: "Enter your email below and we'll send you a link to reset your password.",
reset_password_route_title: "Reset your password",
reset_password_route_description: "Enter your new password below",
login_route_title: "Welcome back",
login_route_description: "Sign in and take your content to the next level.",
manage_environment_route_title: "Manage {{title}}",
manage_environment_route_description: "Environments are a top level grouping of collections, forms and bricks. With the seperation of environments, you can have multiple sites/apps running on the same CMS.",
create_environment_route_title: "Create Environment",
create_environment_route_description: "Environments are a top level grouping of collections, forms and bricks. With the seperation of environments, you can have multiple sites/apps running on the same CMS.",
users_route_title: "Users",
users_route_description: "Manage users and their permissions.",
media_route_title: "Media",
media_route_description: "Manage your media library and upload files.",
settings_route_title: "Settings",
settings_route_description: "Get an overview of your settings and manage integrations.",
dashboard_route_title: "Welcome back{{name}}",
dashboard_route_description: "Get an overview of the latest activity and manage your content.",
email_route_title: "Emails",
email_route_description: "Preview and manage every email sent from the CMS.",
edit_page_route_title: "Edit Page",
edit_page_route_title_singular: "Edit {{title}}",
supported_features: "Supported Features",
supported_features_setting_message: "These are the features that are currently turned on and available for use",
media_enabled: "Media Enabled",
email_enabled: "Emails Enabled",
collection_route_title: "Collections",
collection_route_description: "View and manage the collections and their documents.",
document_route_title: "{{mode}} {{name}}",
default: "Default",
locales: "Locales",
locales_setting_message: "View the available content locales you have access to when editing content",
coming_soon_title: "Coming Soon!",
coming_soon_description: "This feature is currently in development and will be available soon.",
content_locales: "Content Locales",
content_locales_description: "These are the locales available to you when editing content such as collection documents or media etc. If you wish to support more locales, please contact your administrator.",
cms_locale: "Current Locale",
cms_locale_description: "Select the locale you wish to use.",
starting_point_collections: "Collections",
starting_point_collections_description: "View the available collections and click into them to manage their documents and content.",
starting_point_media: "Media",
starting_point_media_description: "View and curate your media library and upload new files.",
starting_point_emails: "Emails",
starting_point_emails_description: "Preview and manage all internal and external emails sent from the CMS.",
starting_point_users: "Users",
starting_point_users_description: "View and mange all users. Assign roles and permissions and invite new users.",
starting_point_roles: "Roles",
starting_point_roles_description: "Create and manage roles and what permissions they give to your users.",
starting_point_settings: "Settings",
starting_point_settings_description: "Get an overview of your current settings, update them and controll integrations.",
media_support_config_stategy_error: "Media support is currently disabled due to no config stategy being present. Please add one to allow media to work.",
email_support_config_stategy_error: "Email support is currently disabled due to no config stategy being present. Please add one to allow emails to work.",
account_details: "Your Details",
account_details_description: "Update your account details.",
configuration: "Configuration",
configuration_description: "Manage config settings specific to your account.",
error_fetching_refresh_token: "There was an error fetching the refresh token.",
unauthorised: "Unauthorised",
this_filed_has_errors_in_other_locales: "This field has errors in other locales",
no_emails: "No Emails Found",
no_emails_description: "We cannot find any emails. It doesn't look like any emails have been sent yet.",
option_label: "Option {{count}}",
client_integrations: "Client Integrations",
client_integration_description: "Set up and mange your client integrations so you can request and use data from Lucid in your own applications",
create_integration: "Create Integration",
create_a_new_integration: "Create New Integration",
client_integration: "Client Integration",
no_client_integrations_found_title: "No Client Integrations Found",
no_client_integrations_found_descriptions: "You currently have no client integrations. Creating an integration will allow you to request and use data from Lucid in your own applications.",
multiple_documents: "Multiple Documents",
single_documents: "Single Documents",
media_upload_error: "Media Upload Error",
media_upload_error_description: "An error occurred while uploading the file. Please try again.",
media_no_key_or_presigned_url: "No key or presigned url found",
locked_document_message: "This document is locked and cannot be edited, only viewed.",
created: "Created",
modified: "Modified",
save_draft: "Save Draft",
optional: "Optional",
collection_needs_migrating_message: "Collection schema changes detected. Please contact your administrator to run a migration.",
this_field_is_missing_from_the_database: "This field is missing from the database.",
this_field_supports_translations: "This field supports translations.",
auto_save: "Auto Save",
auto_save_message: "Your changes are automatically saved every 10 seconds.",
disabled: "Disabled",
saving: "Saving Document"
};
// src/translations/index.ts
var [getLocale, setLocale] = createSignal("en");
i18next.init({
lng: getLocale(),
debug: false,
resources: {
en: {
translation: en_default
}
},
fallbackLng: "en"
});
var T = createMemo(() => {
i18next.changeLanguage(getLocale());
document.documentElement.lang = getLocale();
return i18next.t.bind(i18next);
});
var localesConfig = [
{
code: "en",
name: "English",
default: true
}
];
var translations_default = T;
var _tmpl$ = /* @__PURE__ */ template(`<p class=text-sm>`);
var _tmpl$2 = /* @__PURE__ */ template(`<div><div class="z-10 relative flex pr-10"><span></span><div class=ml-2.5><p class="text-sm font-bold mb-1"></p></div></div><button data-panel-ignore class="bg-container-4 hover:bg-container-3 flex justify-center top-1/2 -translate-y-1/2 items-center w-6 h-6 right-2.5 absolute rounded-full z-20 hover:text-error-base duration-200 transition-all shadow-md"type=button>×</button><span class="inset-0 absolute bg-border opacity-50 z-0">`);
var CustomToast = (props) => {
const [life, setLife] = createSignal(100);
const startTime = Date.now();
const duration = createMemo(() => {
return props.duration || 5e3;
});
createEffect(() => {
if (props.toast.paused) return;
const interval = setInterval(() => {
const diff = Date.now() - startTime - props.toast.pauseDuration;
const percentage = diff / duration() * 100;
if (percentage <= 100) setLife(percentage);
});
onCleanup(() => clearInterval(interval));
});
return (() => {
var _el$ = _tmpl$2(), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild, _el$4 = _el$3.nextSibling, _el$5 = _el$4.firstChild, _el$7 = _el$2.nextSibling, _el$8 = _el$7.nextSibling;
insert(_el$3, createComponent(Switch, {
get children() {
return [createComponent(Match, {
get when() {
return props.type === "success";
},
get children() {
return createComponent(FaSolidCheck, {
"class": "w-3 h-3 m-auto"
});
}
}), createComponent(Match, {
get when() {
return props.type === "error";
},
get children() {
return createComponent(FaSolidExclamation, {
"class": "w-3 h-3 m-auto"
});
}
}), createComponent(Match, {
get when() {
return props.type === "warning";
},
get children() {
return createComponent(FaSolidTriangleExclamation, {
"class": "w-3 h-3 m-auto"
});
}
}), createComponent(Match, {
get when() {
return props.type === "info";
},
get children() {
return createComponent(FaSolidInfo, {
"class": "w-3 h-3 m-auto"
});
}
})];
}
}));
insert(_el$5, () => props.title);
insert(_el$4, createComponent(Show, {
get when() {
return props.message;
},
get children() {
var _el$6 = _tmpl$();
insert(_el$6, () => props.message);
return _el$6;
}
}), null);
_el$7.$$click = () => toast.dismiss(props.toast.id);
effect((_p$) => {
var _v$ = classNames14("bg-container-1 rounded-md p-3 drop-shadow-md w-[400px] relative overflow-hidden", {
"animate-enter": props.toast.visible,
"animate-leave": !props.toast.visible
}), _v$2 = classNames14("w-6 h-6 flex items-center justify-center rounded-full min-w-[24px]", {
"bg-primary-base text-primary-contrast": props.type === "success" || props.type === "info",
"bg-error-base text-white": props.type === "error",
"bg-warning-base text-primary-contrast": props.type === "warning"
}), _v$3 = `${life()}%`;
_v$ !== _p$.e && className(_el$, _p$.e = _v$);
_v$2 !== _p$.t && className(_el$3, _p$.t = _v$2);
_v$3 !== _p$.a && ((_p$.a = _v$3) != null ? _el$8.style.setProperty("width", _v$3) : _el$8.style.removeProperty("width"));
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0
});
return _el$;
})();
};
var CustomToast_default = CustomToast;
delegateEvents(["click"]);
// src/utils/spawn-toast.tsx
var spawnToast = (props) => {
toast2.custom((t) => createComponent(CustomToast_default, {
toast: t,
get title() {
return props.title;
},
get message() {
return props.message;
},
get type() {
return props.status || "info";
},
get duration() {
return props.duration;
}
}), {
id: `${props.title}-${props.message}-${props.status}`
});
};
var spawn_toast_default = spawnToast;
// src/utils/error-handling.ts
var LucidError = class extends Error {
errorRes;
constructor(message, errorRes) {
super(message);
this.name = this.constructor.name;
this.errorRes = errorRes;
}
};
var validateSetError = (error) => {
if (error instanceof LucidError) {
return error.errorRes;
}
return {
status: 500,
name: translations_default()("error"),
message: translations_default()("unknown_error_message"),
errors: {}
};
};
var handleSiteErrors = (error) => {
spawn_toast_default({
title: error.name,
message: error.message,
status: "error"
});
};
// src/utils/query-builder.ts
var queryBuilder = (query) => {
const params = new URLSearchParams(query.queryString || "");
if (query.include !== void 0 && Object.keys(query.include).length > 0) {
let includeString = params.get("include") || "";
for (const key of Object.keys(query.include)) {
if (query.include?.[key]) {
includeString += `${key},`;
}
}
includeString = includeString.slice(0, -1);
if (includeString.length > 0) params.append("include", includeString);
}
if (query.exclude !== void 0 && Object.keys(query.exclude).length > 0) {
let excludeString = params.get("exclude") || "";
for (const key of Object.keys(query.exclude)) {
if (query.exclude?.[key]) {
excludeString += `${key},`;
}
}
excludeString = excludeString.slice(0, -1);
if (excludeString.length > 0) params.append("exclude", excludeString);
}
if (query.filters !== void 0 && Object.keys(query.filters).length > 0) {
for (const key of Object.keys(query.filters)) {
const value = query.filters ? query.filters[key] : "";
if (value === void 0 || value === null) return;
if (Array.isArray(value)) {
params.append(`filter[${key}]`, value.join(","));
}
if (typeof value === "string" || typeof value === "number") {
params.append(`filter[${key}]`, value.toString());
}
}
}
if (query.perPage !== void 0) {
params.append("perPage", query.perPage.toString());
}
return params.toString();
};
var query_builder_default = queryBuilder;
var deepMerge = (obj1, obj2) => {
const result = { ...obj1 };
for (const key in obj2) {
if (Object.prototype.hasOwnProperty.call(obj2, key)) {
if (typeof obj2[key] === "object" && obj2[key] !== null && !Array.isArray(obj2[key]) && obj1[key]) {
result[key] = deepMerge(obj1[key], obj2[key]);
} else {
result[key] = obj2[key];
}
}
}
return result;
};
var deepDiff = (obj1, obj2) => {
const result = {};
for (const key in obj1) {
if (Object.prototype.hasOwnProperty.call(obj1, key)) {
if (Array.isArray(obj1[key])) {
if (!equal(obj1[key], obj2[key])) {
result[key] = obj2[key];
}
} else if (typeof obj1[key] === "object" && obj1[key] !== null) {
const diff = deepDiff(obj1[key], obj2[key]);
if (Object.keys(diff).length > 0) {
result[key] = diff;
}
} else {
if (obj1[key] !== obj2[key]) {
result[key] = obj2[key];
}
}
}
}
for (const key in obj2) {
if (Object.prototype.hasOwnProperty.call(obj2, key) && !Object.prototype.hasOwnProperty.call(obj1, key)) {
result[key] = obj2[key];
}
}
return result;
};
var updateData = (obj1, obj2) => {
const result = deepDiff(obj1, obj2);
return {
changed: Object.keys(result).length > 0,
data: result
};
};
var resolveValue = (value) => (
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
typeof value === "function" ? value() : value
);
var bytesToSize = (bytes) => {
if (!bytes) return "0 Byte";
const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
if (bytes === 0) return "0 Byte";
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return `${Math.round(bytes / 1024 ** i)} ${sizes[i]}`;
};
var getMediaType = (mimeType) => {
if (!mimeType) return "unknown";
const normalizedMimeType = mimeType.toLowerCase();
if (normalizedMimeType.includes("image")) return "image";
if (normalizedMimeType.includes("video")) return "video";
if (normalizedMimeType.includes("audio")) return "audio";
if (normalizedMimeType.includes("pdf") || normalizedMimeType.startsWith("application/vnd"))
return "document";
if (normalizedMimeType.includes("zip") || normalizedMimeType.includes("tar"))
return "archive";
return "unknown";
};
var formatUserName = (user, pref) => {
if (pref === "username") {
if (user.firstName && user.lastName) {
return `${user.username} (${user.firstName} ${user.lastName})`;
}
if (user.firstName) {
return `${user.username} (${user.firstName})`;
}
return user.username;
}
if (user.firstName && user.lastName) {
return `${user.firstName} ${user.lastName} - (${user.username})`;
}
if (user.firstName) {
return `${user.firstName} - (${user.username})`;
}
return user.username;
};
var formatUserInitials = (user) => {
if (user.firstName && user.lastName) {
return `${user.firstName[0]}${user.lastName[0]}`;
}
if (user.firstName) {
return `${user.firstName[0]}${user.firstName[1]}`;
}
return `${user.username[0]}${user.username[1]}`;
};
var updateTranslation = (setter, translation) => {
if (!setter) return;
if (!translation) return;
setter((prev) => {
const index = prev.findIndex(
(t) => t.localeCode === translation.localeCode
);
if (index === -1) return [...prev, translation];
return prev.map(
(item) => item.localeCode === translation.localeCode ? translation : item
);
});
};
var getTranslation = (translations, contentLocale) => {
const translation = translations?.find((t) => t.localeCode === contentLocale);
return translation?.value ?? null;
};
var getRecordTranslation = (translations, contentLocale) => {
if (!contentLocale) return null;
if (!translations) return null;
return translations[contentLocale] ?? null;
};
var getLocaleValue = (props) => {
if (props.value === void 0 || props.value === null)
return props.fallback ?? "";
if (typeof props.value === "string") return props.value;
return props.value[getLocale()] ?? props.fallback ?? "";
};
var helpers = {
deepMerge,
deepDiff,
updateData,
resolveValue,
bytesToSize,
getMediaType,
formatUserName,
formatUserInitials,
updateTranslation,
getTranslation,
getRecordTranslation,
getLocaleValue
};
var helpers_default = helpers;
// src/utils/service-helpers.ts
var resolveObject = (obj) => {
if (!obj) return obj;
const result = {};
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
result[key] = helpers_default.resolveValue(obj[key]);
}
}
return result;
};
var getQueryParams = (params) => {
const { queryString, filters, location, headers, include, perPage, exclude } = params;
return {
queryString: helpers_default.resolveValue(queryString),
filters: resolveObject(filters),
location: resolveObject(location),
headers: resolveObject(headers),
include: resolveObject(include),
exclude: resolveObject(exclude),
perPage: helpers_default.resolveValue(perPage)
};
};
var getQueryKey = (params) => {
return JSON.stringify(params);
};
var useMutationWrapper = ({
mutationFn,
getSuccessToast,
getErrorToast,
invalidates = [],
onSuccess,
onError,
onMutate
}) => {
const [errors, setErrors] = createSignal();
const queryClient = useQueryClient();
const mutation = createMutation(() => ({
mutationFn,
onSettled: (data, error) => {
if (data) {
if (getSuccessToast) {
const successToastData = getSuccessToast();
spawn_toast_default({
title: successToastData.title,
message: successToastData.message,
status: "success"
});
}
setErrors(void 0);
if (onSuccess) onSuccess(data);
for (const query of invalidates) {
queryClient.invalidateQueries({
queryKey: [query]
});
}
} else if (error) {
if (getErrorToast) {
const errorToast = getErrorToast();
spawn_toast_default({
title: errorToast.title,
message: errorToast.message,
status: "error"
});
}
const errors2 = validateSetError(error);
setErrors(errors2);
onError?.(errors2);
}
},
onMutate
}));
onCleanup(() => {
setErrors(void 0);
});
return {
action: mutation,
errors,
reset: () => {
setErrors(void 0);
mutation.reset();
}
};
};
var serviceHelpers = {
getQueryParams,
getQueryKey,
useMutationWrapper
};
var service_helpers_default = serviceHelpers;
// src/services/api/auth/useCsrf.tsx
var csrfSessionKey = "_csrf";
var csrfReq = async () => {
const csrfToken = sessionStorage.getItem(csrfSessionKey);
if (csrfToken) {
return csrfToken;
}
const res = await request_default({
url: "/api/v1/auth/csrf",
config: {
method: "GET"
}
});
if (res.data) {
sessionStorage.setItem(csrfSessionKey, res.data._csrf);
return res.data._csrf;
}
return null;
};
var clearCsrfSession = () => {
sessionStorage.removeItem(csrfSessionKey);
};
var useCsrf = (props) => {
return service_helpers_default.useMutationWrapper({
mutationFn: csrfReq,
onSuccess: props.onSuccess,
onError: props.onError
});
};
var useCsrf_default = useCsrf;
var [getRunning, setRunning] = createSignal(false);
var [refreshTokenPromise, setRefreshTokenPromise] = createSignal(null);
var useRefreshToken = async (params) => {
if (getRunning()) {
await refreshTokenPromise();
return request_default(params);
}
setRunning(true);
const promise = refreshTokenReq();
setRefreshTokenPromise(() => promise);
const successful = await promise;
setRunning(false);
setRefreshTokenPromise(null);
if (!successful) {
throw new LucidError(translations_default()("error_fetching_refresh_token"), {
status: 401,
name: translations_default()("unauthorised"),
message: translations_default()("error_fetching_refresh_token")
});
}
return request_default(params);
};
var refreshTokenReq = async () => {
const fetchURL = getFetchURL("/api/v1/auth/token");
const csrfToken = await csrfReq();
const refreshRes = await fetch(fetchURL, {
method: "POST",
credentials: "include",
headers: {
_csrf: csrfToken || ""
}
});
return refreshRes.status === 204;
};
var useRefreshToken_default = useRefreshToken;
// src/utils/request.ts
var getFetchURL = (url, query) => {
let targetUrl = import.meta.env.PROD ? url : `${import.meta.env.VITE_API_DEV_URL}${url}`;
if (query) {
const queryString = query_builder_default(query);
if (queryString) targetUrl += `?${queryString}`;
}
return targetUrl;
};
var prepareRequestBody = (body) => {
if (!body) return void 0;
return body instanceof FormData ? body : JSON.stringify(body);
};
var prepareHeaders = async (csrf, headers = {}, body) => {
const updatedHeaders = { ...headers };
if (csrf) {
const csrfToken = await csrfReq();
if (csrfToken) updatedHeaders._csrf = csrfToken;
}
if (headers["Content-Type"] === void 0 && typeof body === "string") {
updatedHeaders["Content-Type"] = "application/json";
}
return updatedHeaders;
};
var handleResponse = async (params, fetchRes) => {
if (fetchRes.status === 204) {
return {};
}
const data = await fetchRes.json();
if (fetchRes.status === 401) {
if (data.code === "authorisation") {
return useRefreshToken_default(params);
}
}
if (fetchRes.status === 403) {
if (data.code === "csrf") {
clearCsrfSession();
return await request(params);
}
}
if (fetchRes.status === 429) {
handleSiteErrors(data);
throw new LucidError(
data.message,
data
);
}
if (!fetchRes.ok) {
handleSiteErrors(data);
throw new LucidError(
data.message,
data
);
}
return data;
};
var request = async (params) => {
const fetchURL = getFetchURL(params.url, params.query);
const body = prepareRequestBody(params.config?.body);
const headers = await prepareHeaders(
params.csrf,
params.config?.headers,
body
);
const fetchRes = await fetch(fetchURL, {
method: params.config?.method,
credentials: "include",
body,
headers
});
return handleResponse(params, fetchRes);
};
var request_default = request;
// src/services/api/auth/useLogin.tsx
var loginReq = (params) => {
return request_default({
url: "/api/v1/auth/login",
csrf: true,
config: {
method: "POST",
body: params
}
});
};
var useLogin = (props) => {
const navigate = useNavigate();
return service_helpers_default.useMutationWrapper({
mutationFn: loginReq,
getSuccessToast: () => ({
title: translations_default()("login_success_toast_title"),
message: translations_default()("login_success_toast_message")
}),
invalidates: ["roles.getMultiple", "roles.getSingle"],
onSuccess: () => {
navigate("/admin");
props?.onSuccess?.();
},
onError: props?.onError
});
};
var useLogin_default = useLogin;
var [get, set] = createStore({
user: null,
reset() {
set("user", null);
},
// -----------------
// Permissions
hasPermission(perm) {
if (this.user?.superAdmin) return { all: true, some: true };
const userPerms = this.user?.permissions;
if (!userPerms) return { all: false, some: false };
const all = perm.every((p) => userPerms.includes(p));
const some = perm.some((p) => userPerms.includes(p));
return { all, some };
}
});
var userStore = {
get,
set
};
var userStore_default = userStore;
// src/services/api/auth/useLogout.tsx
var logoutReq = () => {
return request_default({
url: "/api/v1/auth/logout",
csrf: true,
config: {
method: "POST"
}
});
};
var useLogout = (props) => {
const navigate = useNavigate();
return service_helpers_default.useMutationWrapper({
mutationFn: logoutReq,
getSuccessToast: () => ({
title: translations_default()("logout_success_toast_title"),
message: translations_default()("logout_success_toast_message")
}),
invalidates: ["roles.getMultiple", "roles.getSingle"],
onSuccess: () => {
userStore_default.get.reset();
navigate("/admin/login");
clearCsrfSession();
props?.onSuccess?.();
},
onError: () => {
navigate("/admin/login");
props?.onError?.();
}
});
};
var useLogout_default = useLogout;
var useSetupRequired = (params) => {
const queryParams = createMemo(() => service_helpers_default.getQueryParams(params?.queryParams || {}));
const queryKey = createMemo(() => service_helpers_default.getQueryKey(queryParams()));
return createQuery(() => ({
queryKey: ["auth.setupRequired", queryKey(), params?.key?.()],
queryFn: () => request_default({
url: "/api/v1/auth/setup-required",
config: {
method: "GET"
}
}),
get enabled() {
return params?.enabled ? params.enabled() : true;
},
retry: false,
refetchOnWindowFocus: false
}));
};
var useSetupRequired_default = useSetupRequired;
var setupReq = (params) => {
return request_default({
url: "/api/v1/auth/setup",
csrf: true,
config: {
method: "POST",
body: params
}
});
};
var useSetup = (props) => {
const navigate = useNavigate();
return service_helpers_default.useMutationWrapper({
mutationFn: setupReq,
getSuccessToast: () => ({
title: translations_default()("setup_success_toast_title"),
message: translations_default()("setup_success_toast_message")
}),
invalidates: ["auth.setupRequired"],
onSuccess: () => {
navigate("/admin/login");
props?.onSuccess?.();
},
onError: props?.onError
});
};
var useSetup_default = useSetup;
// src/services/api/auth/index.ts
var exportObject = {
useLogin: useLogin_default,
useCsrf: useCsrf_default,
useLogout: useLogout_default,
useSetupRequired: useSetupRequired_default,
useSetup: useSetup_default
};
var auth_default = exportObject;
// src/services/api/account/useForgotPassword.tsx
var sendPasswordResetReq = (params) => {
return request_default({
url: "/api/v1/account/reset-password",
csrf: true,
config: {
method: "POST",
body: params
}
});
};
var useForgotPassword = (props) => {
return service_helpers_default.useMutationWrapper({
mutationFn: sendPasswordResetReq,
getS