mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-08-17 16:07:18 +02:00
Dark mode and various smaller UI improvements
This commit is contained in:
+285
-2
@@ -45,9 +45,292 @@
|
||||
src: url('/Phudu-Black.ttf') format('truetype');
|
||||
}
|
||||
|
||||
/* custom properties for theming */
|
||||
:root {
|
||||
/* light mode colors */
|
||||
--color-bg-primary: #ffffff;
|
||||
--color-bg-secondary: #f8f9fa;
|
||||
--color-bg-tertiary: #f1f3f4;
|
||||
--color-border: #e5e7eb;
|
||||
--color-border-hover: #d1d5db;
|
||||
--color-text-primary: #111827;
|
||||
--color-text-secondary: #6b7280;
|
||||
--color-text-tertiary: #9ca3af;
|
||||
--color-shadow: rgba(0, 0, 0, 0.1);
|
||||
--color-shadow-lg: rgba(0, 0, 0, 0.15);
|
||||
|
||||
/* form colors */
|
||||
--color-input-bg: #ffffff;
|
||||
--color-input-border: #d1d5db;
|
||||
--color-input-border-focus: #2563eb;
|
||||
|
||||
/* scrollbar colors */
|
||||
--color-scrollbar-track: #f1f1f1;
|
||||
--color-scrollbar-thumb: #819efb;
|
||||
--color-scrollbar-thumb-hover: #6b85d6;
|
||||
}
|
||||
|
||||
.dark {
|
||||
/* dark mode colors */
|
||||
--color-bg-primary: #111827;
|
||||
--color-bg-secondary: #1f2937;
|
||||
--color-bg-tertiary: #374151;
|
||||
--color-border: #374151;
|
||||
--color-border-hover: #4b5563;
|
||||
--color-text-primary: #f9fafb;
|
||||
--color-text-secondary: #d1d5db;
|
||||
--color-text-tertiary: #9ca3af;
|
||||
--color-shadow: rgba(0, 0, 0, 0.3);
|
||||
--color-shadow-lg: rgba(0, 0, 0, 0.4);
|
||||
|
||||
/* form colors */
|
||||
--color-input-bg: #374151;
|
||||
--color-input-border: #4b5563;
|
||||
--color-input-border-focus: #3b82f6;
|
||||
|
||||
/* scrollbar colors */
|
||||
--color-scrollbar-track: #374151;
|
||||
--color-scrollbar-thumb: #6b7280;
|
||||
--color-scrollbar-thumb-hover: #9ca3af;
|
||||
}
|
||||
|
||||
/* global styles */
|
||||
body {
|
||||
background-color: var(--color-bg-primary);
|
||||
color: var(--color-text-primary);
|
||||
transition:
|
||||
background-color 0.2s ease,
|
||||
color 0.2s ease;
|
||||
}
|
||||
|
||||
/* scrollbar styles */
|
||||
body {
|
||||
@apply [&::-webkit-scrollbar]:w-2
|
||||
[&::-webkit-scrollbar-track]:bg-gray-100
|
||||
[&::-webkit-scrollbar-track]:bg-[var(--color-scrollbar-track)]
|
||||
[&::-webkit-scrollbar-thumb]:rounded-md
|
||||
[&::-webkit-scrollbar-thumb]:bg-pc-dusty-light-blue;
|
||||
[&::-webkit-scrollbar-thumb]:bg-[var(--color-scrollbar-thumb)]
|
||||
[&::-webkit-scrollbar-thumb:hover]:bg-[var(--color-scrollbar-thumb-hover)];
|
||||
}
|
||||
|
||||
/* dark mode utility classes */
|
||||
@layer utilities {
|
||||
.bg-theme-primary {
|
||||
background-color: var(--color-bg-primary);
|
||||
}
|
||||
|
||||
.bg-theme-secondary {
|
||||
background-color: var(--color-bg-secondary);
|
||||
}
|
||||
|
||||
.bg-theme-tertiary {
|
||||
background-color: var(--color-bg-tertiary);
|
||||
}
|
||||
|
||||
.text-theme-primary {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.text-theme-secondary {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.text-theme-tertiary {
|
||||
color: var(--color-text-tertiary);
|
||||
}
|
||||
|
||||
.border-theme {
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
|
||||
.border-theme-hover:hover {
|
||||
border-color: var(--color-border-hover);
|
||||
}
|
||||
|
||||
.shadow-theme {
|
||||
box-shadow: 0 1px 3px 0 var(--color-shadow);
|
||||
}
|
||||
|
||||
.shadow-theme-lg {
|
||||
box-shadow: 0 10px 15px -3px var(--color-shadow-lg);
|
||||
}
|
||||
|
||||
.input-theme {
|
||||
background-color: var(--color-input-bg);
|
||||
border-color: var(--color-input-border);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.input-theme:focus {
|
||||
border-color: var(--color-input-border-focus);
|
||||
}
|
||||
}
|
||||
|
||||
/* component-specific dark mode overrides */
|
||||
.dark .bg-gray-50 {
|
||||
@apply bg-gray-800;
|
||||
}
|
||||
|
||||
.dark .bg-gray-100 {
|
||||
@apply bg-gray-700;
|
||||
}
|
||||
|
||||
.dark .bg-gray-200 {
|
||||
@apply bg-gray-600;
|
||||
}
|
||||
|
||||
.dark .bg-white {
|
||||
@apply bg-gray-800;
|
||||
}
|
||||
|
||||
.dark .text-gray-900 {
|
||||
@apply text-gray-100;
|
||||
}
|
||||
|
||||
.dark .text-gray-800 {
|
||||
@apply text-gray-200;
|
||||
}
|
||||
|
||||
.dark .text-gray-700 {
|
||||
@apply text-gray-300;
|
||||
}
|
||||
|
||||
.dark .text-gray-600 {
|
||||
@apply text-gray-400;
|
||||
}
|
||||
|
||||
.dark .text-black {
|
||||
@apply text-white;
|
||||
}
|
||||
|
||||
.dark .border-gray-200 {
|
||||
@apply border-gray-600;
|
||||
}
|
||||
|
||||
.dark .border-gray-300 {
|
||||
@apply border-gray-500;
|
||||
}
|
||||
|
||||
/* table dark mode styles */
|
||||
.dark table {
|
||||
@apply bg-gray-800;
|
||||
}
|
||||
|
||||
.dark table td {
|
||||
@apply bg-transparent text-gray-300 border-gray-600;
|
||||
}
|
||||
|
||||
/* form elements dark mode */
|
||||
.dark input,
|
||||
.dark textarea,
|
||||
.dark select {
|
||||
@apply bg-gray-700 border-gray-600 text-white placeholder-gray-400;
|
||||
}
|
||||
|
||||
.dark input:focus,
|
||||
.dark textarea:focus,
|
||||
.dark select:focus {
|
||||
@apply border-blue-500 ring-blue-500;
|
||||
}
|
||||
|
||||
/* modal and card dark mode */
|
||||
.dark .modal-content,
|
||||
.dark .card {
|
||||
@apply bg-gray-800 border-gray-600;
|
||||
}
|
||||
|
||||
/* button hover effects in dark mode */
|
||||
.dark .bg-cta-blue:hover {
|
||||
@apply bg-blue-600;
|
||||
}
|
||||
|
||||
.dark .bg-pc-darkblue:hover {
|
||||
@apply bg-blue-900;
|
||||
}
|
||||
|
||||
/* custom scrollbar for dark mode containers */
|
||||
.dark .overflow-auto,
|
||||
.dark .overflow-x-auto,
|
||||
.dark .overflow-y-auto {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #6b7280 #374151;
|
||||
}
|
||||
|
||||
.dark .overflow-auto::-webkit-scrollbar,
|
||||
.dark .overflow-x-auto::-webkit-scrollbar,
|
||||
.dark .overflow-y-auto::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.dark .overflow-auto::-webkit-scrollbar-track,
|
||||
.dark .overflow-x-auto::-webkit-scrollbar-track,
|
||||
.dark .overflow-y-auto::-webkit-scrollbar-track {
|
||||
background: #374151;
|
||||
}
|
||||
|
||||
.dark .overflow-auto::-webkit-scrollbar-thumb,
|
||||
.dark .overflow-x-auto::-webkit-scrollbar-thumb,
|
||||
.dark .overflow-y-auto::-webkit-scrollbar-thumb {
|
||||
background: #6b7280;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.dark .overflow-auto::-webkit-scrollbar-thumb:hover,
|
||||
.dark .overflow-x-auto::-webkit-scrollbar-thumb:hover,
|
||||
.dark .overflow-y-auto::-webkit-scrollbar-thumb:hover {
|
||||
background: #9ca3af;
|
||||
}
|
||||
|
||||
/* native date picker dark mode styling */
|
||||
.dark input[type='date']::-webkit-calendar-picker-indicator,
|
||||
.dark input[type='time']::-webkit-calendar-picker-indicator,
|
||||
.dark input[type='datetime-local']::-webkit-calendar-picker-indicator {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
.dark input[type='date']::-webkit-datetime-edit,
|
||||
.dark input[type='time']::-webkit-datetime-edit,
|
||||
.dark input[type='datetime-local']::-webkit-datetime-edit {
|
||||
color: rgb(209 213 219);
|
||||
}
|
||||
|
||||
.dark input[type='date']::-webkit-datetime-edit-fields-wrapper,
|
||||
.dark input[type='time']::-webkit-datetime-edit-fields-wrapper,
|
||||
.dark input[type='datetime-local']::-webkit-datetime-edit-fields-wrapper {
|
||||
background: rgb(55 65 81);
|
||||
}
|
||||
|
||||
/* force date picker to use system dark theme */
|
||||
.dark input[type='date'],
|
||||
.dark input[type='time'],
|
||||
.dark input[type='datetime-local'] {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
/* selected date background in dark mode */
|
||||
.dark input[type='date']::-webkit-datetime-edit-day-field:focus,
|
||||
.dark input[type='date']::-webkit-datetime-edit-month-field:focus,
|
||||
.dark input[type='date']::-webkit-datetime-edit-year-field:focus,
|
||||
.dark input[type='time']::-webkit-datetime-edit-hour-field:focus,
|
||||
.dark input[type='time']::-webkit-datetime-edit-minute-field:focus {
|
||||
background-color: rgb(55 65 81);
|
||||
color: rgb(209 213 219);
|
||||
}
|
||||
|
||||
/* transitions for smooth theme switching */
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
transition:
|
||||
background-color 0.2s ease,
|
||||
border-color 0.2s ease,
|
||||
color 0.2s ease;
|
||||
}
|
||||
|
||||
/* override transitions for elements that shouldn't animate */
|
||||
.no-transition,
|
||||
.no-transition *,
|
||||
.no-transition *::before,
|
||||
.no-transition *::after {
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
@@ -243,7 +243,9 @@
|
||||
</script>
|
||||
|
||||
{#if visible}
|
||||
<div class="fixed top-0 left-0 w-full h-full bg-cta-blue opacity-20 blur-xl" />
|
||||
<div
|
||||
class="fixed top-0 left-0 w-full h-full bg-cta-blue dark:bg-gray-900 opacity-20 blur-xl transition-colors duration-200"
|
||||
/>
|
||||
<div
|
||||
class="fixed top-0 left-0 w-full h-full flex justify-center items-center backdrop-blur-sm z-20"
|
||||
role="dialog"
|
||||
@@ -253,11 +255,11 @@
|
||||
>
|
||||
<section
|
||||
bind:this={alertElement}
|
||||
class="shadow-xl w-[32rem] bg-white opacity-100 rounded-md flex flex-col"
|
||||
class="shadow-xl dark:shadow-gray-900/70 w-[32rem] bg-white dark:bg-gray-800 opacity-100 rounded-md flex flex-col transition-colors duration-200"
|
||||
>
|
||||
<!-- Header -->
|
||||
<div
|
||||
class="bg-red-700 text-white rounded-t-md py-4 px-8 flex items-center justify-between flex-shrink-0"
|
||||
class="bg-red-700 dark:bg-red-800 text-white rounded-t-md py-4 px-8 flex items-center justify-between flex-shrink-0 transition-colors duration-200"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<svg
|
||||
@@ -289,7 +291,10 @@
|
||||
|
||||
<!-- Content -->
|
||||
<div class="px-8 py-6">
|
||||
<div id="alert-description" class="text-gray-600">
|
||||
<div
|
||||
id="alert-description"
|
||||
class="text-gray-600 dark:text-gray-300 transition-colors duration-200"
|
||||
>
|
||||
{#if $$slots.default}
|
||||
<slot />
|
||||
{:else}
|
||||
@@ -312,13 +317,13 @@
|
||||
|
||||
<!-- Footer -->
|
||||
<div
|
||||
class="py-4 row-span-2 col-start-1 col-span-3 border-t-2 w-full flex flex-row justify-center items-center sm:justify-center md:justify-center lg:justify-end xl:justify-end 2xl:justify-end px-8 bg-gray-50 rounded-b-md"
|
||||
class="py-4 row-span-2 col-start-1 col-span-3 border-t-2 border-gray-200 dark:border-gray-600 w-full flex flex-row justify-center items-center sm:justify-center md:justify-center lg:justify-end xl:justify-end 2xl:justify-end px-8 bg-gray-50 dark:bg-gray-700 rounded-b-md transition-colors duration-200"
|
||||
>
|
||||
{#if !noCancel}
|
||||
<button
|
||||
type="reset"
|
||||
on:click={close}
|
||||
class="bg-slate-400 hover:bg-slate-300 text-sm mr-2 uppercase font-bold px-4 py-2 text-white rounded-md"
|
||||
class="bg-slate-400 hover:bg-slate-300 dark:bg-gray-600 dark:hover:bg-gray-500 text-sm mr-2 uppercase font-bold px-4 py-2 text-white rounded-md transition-colors duration-200"
|
||||
>
|
||||
{cancel}
|
||||
</button>
|
||||
|
||||
@@ -66,15 +66,20 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="relative h-2">
|
||||
<TextFieldSelect
|
||||
id="autoRefresh"
|
||||
value={$autoRefreshStore.enabled
|
||||
? options.byValue($autoRefreshStore.interval.toString())
|
||||
: 'Disabled'}
|
||||
onSelect={handleIntervalChange}
|
||||
options={options.keys()}
|
||||
inline={true}
|
||||
size={'small'}>Auto-Refresh</TextFieldSelect
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-semibold text-slate-600 dark:text-gray-300 py-2 transition-colors duration-200"
|
||||
>Auto-Refresh</span
|
||||
>
|
||||
<div class="relative">
|
||||
<TextFieldSelect
|
||||
id="autoRefresh"
|
||||
value={$autoRefreshStore.enabled
|
||||
? options.byValue($autoRefreshStore.interval.toString())
|
||||
: 'Disabled'}
|
||||
onSelect={handleIntervalChange}
|
||||
options={options.keys()}
|
||||
inline={true}
|
||||
size={'small'}
|
||||
></TextFieldSelect>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
|
||||
<button
|
||||
{disabled}
|
||||
class="self-start mt-6 bg-gradient-to-b from-blue-500 to-indigo-400 px-4 w-56 py-2 hover:from-blue-400 hover:to-indigo-400 text-white font-bold uppercase rounded-md mb-10"
|
||||
class="self-start mt-6 bg-gradient-to-b from-blue-500 to-indigo-400 dark:from-blue-600 dark:to-indigo-500 px-4 w-56 py-2 hover:from-blue-400 hover:to-indigo-400 dark:hover:from-blue-500 dark:hover:to-indigo-400 text-white font-bold uppercase rounded-md mb-10 transition-all duration-200"
|
||||
on:click
|
||||
class:opacity-50={disabled}
|
||||
class:dark:opacity-60={disabled}
|
||||
class:cursor-not-allowed={disabled}
|
||||
{type}
|
||||
>
|
||||
|
||||
@@ -9,10 +9,12 @@
|
||||
</script>
|
||||
|
||||
<button
|
||||
class="{backgroundColor} px-6 py-2 text-white rounded-md uppercase font-semibold hover:opacity-80 text-sm {css}"
|
||||
class="{backgroundColor} px-6 py-2 text-white rounded-md uppercase font-semibold hover:opacity-80 text-sm transition-all duration-200 dark:hover:opacity-90 {css}"
|
||||
class:w-32={size === 'small'}
|
||||
class:w-36={size === 'medium'}
|
||||
class:w-60={size === 'large'}
|
||||
class:disabled:opacity-50={disabled}
|
||||
class:dark:disabled:opacity-60={disabled}
|
||||
{disabled}
|
||||
on:click
|
||||
>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
export let disabled = false;
|
||||
/**
|
||||
* @type {"submit" | "button" | "reset"}
|
||||
|
||||
|
||||
*/
|
||||
export let type = 'submit';
|
||||
</script>
|
||||
@@ -11,7 +11,7 @@
|
||||
<button
|
||||
{disabled}
|
||||
{type}
|
||||
class="w-full p-2 text-white text-2xl bg-cta-blue rounded-lg font-phudu font-bold"
|
||||
class="w-full p-2 text-white text-2xl bg-cta-blue dark:bg-blue-600 hover:bg-blue-700 dark:hover:bg-blue-700 rounded-lg font-phudu font-bold transition-colors duration-200 disabled:opacity-50 dark:disabled:opacity-60"
|
||||
>Login</button
|
||||
>
|
||||
</div>
|
||||
|
||||
@@ -41,11 +41,12 @@
|
||||
|
||||
let isGeneratingCalendar = false;
|
||||
|
||||
// Use consistent colors that match the design system - these work well in both light and dark modes
|
||||
const COLORS = {
|
||||
SCHEDULED: '#62aded',
|
||||
ACTIVE: '#5557f6',
|
||||
COMPLETED: '#69e1ab',
|
||||
SELF_MANAGED: '#9F7AEA'
|
||||
SCHEDULED: '#62aded', // campaign-scheduled
|
||||
ACTIVE: '#5557f6', // campaign-active
|
||||
COMPLETED: '#4cb5b5', // message-read - much more muted than bright green
|
||||
SELF_MANAGED: '#9F7AEA' // purple
|
||||
};
|
||||
|
||||
function sortCampaignsByPriority(campaigns, day) {
|
||||
@@ -306,18 +307,20 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="w-full bg-white rounded-lg shadow-sm p-4 border border-gray-200">
|
||||
<div
|
||||
class="w-full bg-white dark:bg-gray-800 rounded-lg shadow-sm p-4 border border-gray-200 dark:border-gray-700 transition-colors duration-200"
|
||||
>
|
||||
<div class="space-y-4 max-w-5xl mx-auto min-h-[600px]">
|
||||
<!-- Navigation Controls -->
|
||||
<div class="flex justify-center items-center">
|
||||
<button
|
||||
class="p-2 rounded hover:bg-gray-100 mx-4"
|
||||
class="p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-700 mx-4 transition-colors duration-200"
|
||||
on:click={previousMonth}
|
||||
disabled={isLoadingNewMonth}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5 text-gray-600"
|
||||
class="h-5 w-5 text-gray-600 dark:text-gray-300"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@@ -331,18 +334,18 @@
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<h2 class="text-lg font-semibold">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
{format(currentMonth, 'MMMM yyyy')}
|
||||
</h2>
|
||||
|
||||
<button
|
||||
class="p-2 rounded hover:bg-gray-100 mx-4"
|
||||
class="p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-700 mx-4 transition-colors duration-200"
|
||||
on:click={nextMonth}
|
||||
disabled={isLoadingNewMonth}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5 text-gray-600"
|
||||
class="h-5 w-5 text-gray-600 dark:text-gray-300"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@@ -356,7 +359,7 @@
|
||||
<div class="flex justify-center flex-wrap gap-4 text-sm">
|
||||
{#each [{ key: 'SCHEDULED', color: COLORS.SCHEDULED, label: 'Scheduled' }, { key: 'ACTIVE', color: COLORS.ACTIVE, label: 'Active' }, { key: 'COMPLETED', color: COLORS.COMPLETED, label: 'Completed' }, { key: 'SELF_MANAGED', color: COLORS.SELF_MANAGED, label: 'Self-managed' }] as item}
|
||||
<button
|
||||
class="flex items-center cursor-pointer select-none"
|
||||
class="flex items-center cursor-pointer select-none hover:opacity-80 transition-opacity duration-200"
|
||||
on:click={() => toggleFilter(item.key)}
|
||||
>
|
||||
<div
|
||||
@@ -364,7 +367,7 @@
|
||||
style="background-color: {item.color}; opacity: {activeFilters[item.key] ? '1' : '0.3'}"
|
||||
></div>
|
||||
<span
|
||||
class="transition-opacity duration-200"
|
||||
class="transition-opacity duration-200 text-gray-700 dark:text-gray-300"
|
||||
style="opacity: {activeFilters[item.key] ? '1' : '0.5'}">{item.label}</span
|
||||
>
|
||||
</button>
|
||||
@@ -376,7 +379,7 @@
|
||||
<!-- Day headers -->
|
||||
<div class="grid grid-cols-7 text-center mb-1">
|
||||
{#each ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] as day}
|
||||
<div class="text-sm font-medium text-gray-600">{day}</div>
|
||||
<div class="text-sm font-medium text-gray-600 dark:text-gray-300">{day}</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -384,8 +387,10 @@
|
||||
{#if !isInitialized || isGeneratingCalendar || isLoadingNewMonth}
|
||||
<div class="min-h-[450px] flex items-center justify-center">
|
||||
<div class="flex items-center space-x-2">
|
||||
<div class="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600"></div>
|
||||
<span class="text-gray-600">Loading calendar...</span>
|
||||
<div
|
||||
class="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600 dark:border-blue-400"
|
||||
></div>
|
||||
<span class="text-gray-600 dark:text-gray-300">Loading calendar...</span>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
@@ -395,17 +400,19 @@
|
||||
{#each week as day}
|
||||
<div
|
||||
class="calendar-day relative border rounded-md overflow-hidden {scrollBarClassesHorizontal} {day.isToday
|
||||
? 'border-gray-700 bg-gray-50'
|
||||
: 'border-gray-200'}
|
||||
{day.isCurrentMonth ? 'bg-white' : 'bg-gray-50'}"
|
||||
? 'border-gray-700 dark:border-gray-400 bg-gray-50 dark:bg-gray-700'
|
||||
: 'border-gray-200 dark:border-gray-600'}
|
||||
{day.isCurrentMonth
|
||||
? 'bg-white dark:bg-gray-800'
|
||||
: 'bg-gray-50 dark:bg-gray-700'} transition-colors duration-200"
|
||||
>
|
||||
<!-- Date number -->
|
||||
<div
|
||||
class="text-sm p-1 {day.isToday
|
||||
? 'font-bold text-gray-800'
|
||||
? 'font-bold text-gray-800 dark:text-white'
|
||||
: day.isCurrentMonth
|
||||
? 'text-gray-600'
|
||||
: 'text-gray-400'}"
|
||||
? 'text-gray-600 dark:text-gray-300'
|
||||
: 'text-gray-400 dark:text-gray-500'}"
|
||||
>
|
||||
{day.date.getDate()}
|
||||
</div>
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
let chartContainer;
|
||||
let sizingContainer;
|
||||
let width = 300;
|
||||
let height = 200; // Balanced height to prevent overflow
|
||||
let height = 240; // Increased height for better label spacing
|
||||
let containerReady = false;
|
||||
|
||||
// User controls for N
|
||||
@@ -96,7 +96,7 @@
|
||||
$: margin = {
|
||||
top: 15,
|
||||
right: Math.min(180, Math.max(160, width * 0.28)), // 28% of width, min 160px, max 180px
|
||||
bottom: 30,
|
||||
bottom: 50, // Increased bottom margin for better label spacing
|
||||
left: 50
|
||||
};
|
||||
|
||||
@@ -344,17 +344,26 @@
|
||||
|
||||
const stop1 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
|
||||
stop1.setAttribute('offset', '0%');
|
||||
stop1.setAttribute('stop-color', '#f8f9fa');
|
||||
stop1.setAttribute(
|
||||
'stop-color',
|
||||
document.documentElement.classList.contains('dark') ? '#374151' : '#f8f9fa'
|
||||
);
|
||||
stop1.setAttribute('stop-opacity', '0.3');
|
||||
|
||||
const stop2 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
|
||||
stop2.setAttribute('offset', '50%');
|
||||
stop2.setAttribute('stop-color', '#e9ecef');
|
||||
stop2.setAttribute(
|
||||
'stop-color',
|
||||
document.documentElement.classList.contains('dark') ? '#4b5563' : '#e9ecef'
|
||||
);
|
||||
stop2.setAttribute('stop-opacity', '0.5');
|
||||
|
||||
const stop3 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
|
||||
stop3.setAttribute('offset', '100%');
|
||||
stop3.setAttribute('stop-color', '#f8f9fa');
|
||||
stop3.setAttribute(
|
||||
'stop-color',
|
||||
document.documentElement.classList.contains('dark') ? '#374151' : '#f8f9fa'
|
||||
);
|
||||
stop3.setAttribute('stop-opacity', '0.3');
|
||||
|
||||
gradient.appendChild(stop1);
|
||||
@@ -372,7 +381,10 @@
|
||||
line.setAttribute('x2', (width - margin.right).toString());
|
||||
line.setAttribute('y1', y.toString());
|
||||
line.setAttribute('y2', y.toString());
|
||||
line.setAttribute('stroke', '#E5E7EB');
|
||||
line.setAttribute(
|
||||
'stroke',
|
||||
document.documentElement.classList.contains('dark') ? '#4b5563' : '#E5E7EB'
|
||||
);
|
||||
line.setAttribute('stroke-width', '1');
|
||||
line.setAttribute('opacity', '0.4');
|
||||
svg.appendChild(line);
|
||||
@@ -386,7 +398,10 @@
|
||||
vLine.setAttribute('x2', x.toString());
|
||||
vLine.setAttribute('y1', margin.top.toString());
|
||||
vLine.setAttribute('y2', (height - margin.bottom).toString());
|
||||
vLine.setAttribute('stroke', '#e5e7eb');
|
||||
vLine.setAttribute(
|
||||
'stroke',
|
||||
document.documentElement.classList.contains('dark') ? '#4b5563' : '#e5e7eb'
|
||||
);
|
||||
vLine.setAttribute('stroke-width', '0.5');
|
||||
vLine.setAttribute('opacity', '0.3');
|
||||
svg.appendChild(vLine);
|
||||
@@ -399,7 +414,10 @@
|
||||
yAxis.setAttribute('x2', margin.left.toString());
|
||||
yAxis.setAttribute('y1', margin.top.toString());
|
||||
yAxis.setAttribute('y2', (height - margin.bottom).toString());
|
||||
yAxis.setAttribute('stroke', '#6B7280');
|
||||
yAxis.setAttribute(
|
||||
'stroke',
|
||||
document.documentElement.classList.contains('dark') ? '#9ca3af' : '#6B7280'
|
||||
);
|
||||
yAxis.setAttribute('stroke-width', '2');
|
||||
svg.appendChild(yAxis);
|
||||
|
||||
@@ -408,7 +426,10 @@
|
||||
xAxis.setAttribute('x2', (width - margin.right).toString());
|
||||
xAxis.setAttribute('y1', (height - margin.bottom).toString());
|
||||
xAxis.setAttribute('y2', (height - margin.bottom).toString());
|
||||
xAxis.setAttribute('stroke', '#6B7280');
|
||||
xAxis.setAttribute(
|
||||
'stroke',
|
||||
document.documentElement.classList.contains('dark') ? '#9ca3af' : '#6B7280'
|
||||
);
|
||||
xAxis.setAttribute('stroke-width', '2');
|
||||
svg.appendChild(xAxis);
|
||||
|
||||
@@ -420,7 +441,10 @@
|
||||
text.setAttribute('y', (y - 2).toString());
|
||||
text.setAttribute('text-anchor', 'end');
|
||||
text.setAttribute('font-size', '11');
|
||||
text.setAttribute('fill', '#6B7280');
|
||||
text.setAttribute(
|
||||
'fill',
|
||||
document.documentElement.classList.contains('dark') ? '#d1d5db' : '#6B7280'
|
||||
);
|
||||
text.setAttribute('font-weight', '500');
|
||||
text.setAttribute('alignment-baseline', 'middle');
|
||||
text.textContent = `${value}%`;
|
||||
@@ -432,10 +456,13 @@
|
||||
const x = xScale(i);
|
||||
const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
||||
text.setAttribute('x', x.toString());
|
||||
text.setAttribute('y', (height - margin.bottom + 18).toString());
|
||||
text.setAttribute('y', (height - margin.bottom + 25).toString());
|
||||
text.setAttribute('text-anchor', 'middle');
|
||||
text.setAttribute('font-size', '11');
|
||||
text.setAttribute('fill', '#000');
|
||||
text.setAttribute(
|
||||
'fill',
|
||||
document.documentElement.classList.contains('dark') ? '#f9fafb' : '#000'
|
||||
);
|
||||
text.setAttribute('alignment-baseline', 'middle');
|
||||
// Show date (YYYY/MM) and campaign name on the same line
|
||||
const labelSpan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
|
||||
@@ -534,9 +561,9 @@
|
||||
}
|
||||
|
||||
function createLegend(svg, svgRoot) {
|
||||
const legendY = margin.top - 5; // Move legend higher
|
||||
const legendY = margin.top + 5; // Align with chart top area
|
||||
const legendX = width - margin.right + 10;
|
||||
const legendSpacing = 12; // Reduce spacing between legend items
|
||||
const legendSpacing = 18; // More spacing between legend items
|
||||
|
||||
// Build a flat list of legend items (main and moving averages)
|
||||
const legendItems = [];
|
||||
@@ -684,8 +711,14 @@
|
||||
|
||||
const tooltipRect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
||||
tooltipRect.setAttribute('rx', '4');
|
||||
tooltipRect.setAttribute('fill', '#1F2937');
|
||||
tooltipRect.setAttribute('stroke', '#374151');
|
||||
tooltipRect.setAttribute(
|
||||
'fill',
|
||||
document.documentElement.classList.contains('dark') ? '#111827' : '#1F2937'
|
||||
);
|
||||
tooltipRect.setAttribute(
|
||||
'stroke',
|
||||
document.documentElement.classList.contains('dark') ? '#374151' : '#4b5563'
|
||||
);
|
||||
tooltipRect.setAttribute('opacity', '0.95');
|
||||
|
||||
const tooltipText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
||||
@@ -829,18 +862,26 @@
|
||||
<div>
|
||||
{#if debouncedIsLoading}
|
||||
<div class="flex items-center justify-center h-64">
|
||||
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
||||
<span class="ml-2 text-gray-600">Loading trend data...</span>
|
||||
<div
|
||||
class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 dark:border-blue-400"
|
||||
></div>
|
||||
<span class="ml-2 text-gray-600 dark:text-gray-300 transition-colors duration-200"
|
||||
>Loading trend data...</span
|
||||
>
|
||||
</div>
|
||||
{:else if !hasAttemptedLoad && debouncedShowPending}
|
||||
<div class="flex items-center justify-center h-64">
|
||||
<span class="text-gray-400 text-sm">Preparing trend data...</span>
|
||||
<span class="text-gray-400 dark:text-gray-500 text-sm transition-colors duration-200"
|
||||
>Preparing trend data...</span
|
||||
>
|
||||
</div>
|
||||
{:else if hasAttemptedLoad && !isLoading && !debouncedIsLoading && chartData.length === 0}
|
||||
<div class="flex items-center justify-center h-64 bg-gray-50 rounded-lg">
|
||||
<div
|
||||
class="flex items-center justify-center h-64 bg-gray-50 dark:bg-gray-800 rounded-lg transition-colors duration-200"
|
||||
>
|
||||
<div class="text-center">
|
||||
<svg
|
||||
class="mx-auto h-12 w-12 text-gray-400"
|
||||
class="mx-auto h-12 w-12 text-gray-400 dark:text-gray-500 transition-colors duration-200"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -852,17 +893,23 @@
|
||||
d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
|
||||
/>
|
||||
</svg>
|
||||
<h3 class="mt-2 text-sm font-medium text-gray-900">No campaign data</h3>
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
<h3
|
||||
class="mt-2 text-sm font-medium text-gray-900 dark:text-gray-200 transition-colors duration-200"
|
||||
>
|
||||
No campaign data
|
||||
</h3>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400 transition-colors duration-200">
|
||||
Campaign statistics will appear here once campaigns are completed.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{:else if hasAttemptedLoad && !isLoading && !debouncedIsLoading && chartData.length === 1}
|
||||
<div class="bg-blue-50 border border-blue-200 rounded-lg p-6">
|
||||
<div
|
||||
class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-6 transition-colors duration-200"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<svg
|
||||
class="h-6 w-6 text-blue-600 mr-2"
|
||||
class="h-6 w-6 text-blue-600 dark:text-blue-400 mr-2 transition-colors duration-200"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -875,44 +922,78 @@
|
||||
/>
|
||||
</svg>
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-blue-900">Single Campaign Data</h4>
|
||||
<p class="text-sm text-blue-700">
|
||||
<h4
|
||||
class="text-sm font-medium text-blue-900 dark:text-blue-200 transition-colors duration-200"
|
||||
>
|
||||
Single Campaign Data
|
||||
</h4>
|
||||
<p class="text-sm text-blue-700 dark:text-blue-300 transition-colors duration-200">
|
||||
Trends will appear when you have 2 or more completed campaigns.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 gap-4">
|
||||
<div class="text-center">
|
||||
<div class="text-2xl font-bold text-blue-600">{chartData[0].openRate}%</div>
|
||||
<div class="text-sm text-gray-600">Open Rate</div>
|
||||
<div
|
||||
class="text-2xl font-bold text-blue-600 dark:text-blue-400 transition-colors duration-200"
|
||||
>
|
||||
{chartData[0].openRate}%
|
||||
</div>
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400 transition-colors duration-200">
|
||||
Open Rate
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-2xl font-bold text-green-600">{chartData[0].clickRate}%</div>
|
||||
<div class="text-sm text-gray-600">Click Rate</div>
|
||||
<div
|
||||
class="text-2xl font-bold text-green-600 dark:text-green-400 transition-colors duration-200"
|
||||
>
|
||||
{chartData[0].clickRate}%
|
||||
</div>
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400 transition-colors duration-200">
|
||||
Click Rate
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-2xl font-bold text-yellow-600">{chartData[0].submissionRate}%</div>
|
||||
<div class="text-sm text-gray-600">Submission Rate</div>
|
||||
<div
|
||||
class="text-2xl font-bold text-yellow-600 dark:text-yellow-400 transition-colors duration-200"
|
||||
>
|
||||
{chartData[0].submissionRate}%
|
||||
</div>
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400 transition-colors duration-200">
|
||||
Submission Rate
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-2xl font-bold text-indigo-600">{chartData[0].reportRate}%</div>
|
||||
<div class="text-sm text-gray-600">Report Rate</div>
|
||||
<div
|
||||
class="text-2xl font-bold text-indigo-600 dark:text-indigo-400 transition-colors duration-200"
|
||||
>
|
||||
{chartData[0].reportRate}%
|
||||
</div>
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400 transition-colors duration-200">
|
||||
Report Rate
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if hasAttemptedLoad && !isLoading && !debouncedIsLoading && chartData.length >= 2}
|
||||
<div class="bg-white rounded-lg border border-gray-200 p-6">
|
||||
<div
|
||||
class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-600 p-6 transition-colors duration-200"
|
||||
>
|
||||
<!-- Trendline stats and controls above chart -->
|
||||
<div class="flex flex-row items-center justify-between mb-2 pb-0 flex-wrap gap-2">
|
||||
<h4 class="text-sm font-medium text-gray-600 m-0">
|
||||
<h4
|
||||
class="text-sm font-medium text-gray-600 dark:text-gray-300 m-0 transition-colors duration-200"
|
||||
>
|
||||
Trendline: Last {trendStats ? trendStats.n : chartData.length} Campaigns (average)
|
||||
</h4>
|
||||
<div class="flex flex-wrap items-center gap-2 mb-0">
|
||||
<label class="flex items-center gap-1 text-xs text-gray-700">
|
||||
<label
|
||||
class="flex items-center gap-1 text-xs text-gray-700 dark:text-gray-300 transition-colors duration-200"
|
||||
>
|
||||
Time range:
|
||||
<select
|
||||
bind:value={selectedTimeRange}
|
||||
class="border rounded px-1 py-0 text-xs"
|
||||
class="border border-gray-300 dark:border-gray-600 rounded px-1 py-0 text-xs bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-200 transition-colors duration-200"
|
||||
style="height: 1.5rem;"
|
||||
>
|
||||
{#each timeRanges as range}
|
||||
@@ -921,26 +1002,30 @@
|
||||
</select>
|
||||
</label>
|
||||
{#if chartData.length > 1}
|
||||
<label class="flex items-center gap-1 text-xs text-gray-700">
|
||||
<label
|
||||
class="flex items-center gap-1 text-xs text-gray-700 dark:text-gray-300 transition-colors duration-200"
|
||||
>
|
||||
Trendline N:
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
max={chartData.length}
|
||||
bind:value={trendN}
|
||||
class="border rounded px-1 py-0 w-10 text-xs"
|
||||
class="border border-gray-300 dark:border-gray-600 rounded px-1 py-0 w-10 text-xs bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-200 transition-colors duration-200"
|
||||
style="height: 1.5rem;"
|
||||
/>
|
||||
</label>
|
||||
{/if}
|
||||
<label class="flex items-center gap-1 text-xs text-gray-700">
|
||||
<label
|
||||
class="flex items-center gap-1 text-xs text-gray-700 dark:text-gray-300 transition-colors duration-200"
|
||||
>
|
||||
Moving Avg N:
|
||||
<input
|
||||
type="number"
|
||||
min="2"
|
||||
max={chartData.length}
|
||||
bind:value={movingAvgN}
|
||||
class="border rounded px-1 py-0 w-10 text-xs"
|
||||
class="border border-gray-300 dark:border-gray-600 rounded px-1 py-0 w-10 text-xs bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-200 transition-colors duration-200"
|
||||
style="height: 1.5rem;"
|
||||
/>
|
||||
</label>
|
||||
@@ -956,7 +1041,10 @@
|
||||
class="w-3 h-3 rounded-full mr-2"
|
||||
style="background-color: {metric.color}"
|
||||
></div>
|
||||
<span class="text-sm font-medium text-gray-700">{metric.label}</span>
|
||||
<span
|
||||
class="text-sm font-medium text-gray-700 dark:text-gray-300 transition-colors duration-200"
|
||||
>{metric.label}</span
|
||||
>
|
||||
</div>
|
||||
<div class="mt-1">
|
||||
<span class="text-2xl font-bold" style="color: {metric.color}">
|
||||
@@ -971,7 +1059,9 @@
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-center text-gray-400 text-sm py-4">
|
||||
<div
|
||||
class="text-center text-gray-400 dark:text-gray-500 text-sm py-4 transition-colors duration-200"
|
||||
>
|
||||
No trendline stats to display (trendStats is null or not enough data).
|
||||
</div>
|
||||
{/if}
|
||||
@@ -979,7 +1069,7 @@
|
||||
{#if containerReady}
|
||||
<div
|
||||
bind:this={chartContainer}
|
||||
class="min-h-[180px] max-h-[220px] w-full box-border relative rounded-md bg-white m-1"
|
||||
class="min-h-[220px] max-h-[280px] w-full box-border relative rounded-md bg-white dark:bg-gray-800 m-1 transition-colors duration-200"
|
||||
style="contain: layout style;"
|
||||
></div>
|
||||
{/if}
|
||||
@@ -1038,6 +1128,10 @@
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
:global(.dark .campaign-trend-chart) {
|
||||
background: #1f2937 !important;
|
||||
}
|
||||
:global(.line-enhanced) {
|
||||
stroke-width: 5 !important;
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
class:items-center={inline}
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<p class="font-semibold text-slate-600 py-2">
|
||||
<p class="font-semibold text-slate-600 dark:text-gray-300 py-2 transition-colors duration-200">
|
||||
<slot />
|
||||
</p>
|
||||
{#if toolTipText.length > 0}
|
||||
@@ -55,8 +55,10 @@
|
||||
</ToolTip>
|
||||
{/if}
|
||||
{#if optional === true}
|
||||
<div class="bg-gray-100 ml-2 px-2 rounded-md">
|
||||
<p class="text-slate-600 text-xs">optional</p>
|
||||
<div class="bg-gray-100 dark:bg-gray-700 ml-2 px-2 rounded-md transition-colors duration-200">
|
||||
<p class="text-slate-600 dark:text-gray-300 text-xs transition-colors duration-200">
|
||||
optional
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -71,12 +73,12 @@
|
||||
on:change
|
||||
/>
|
||||
<div
|
||||
class="w-5 h-5 border-2 border-slate-300 rounded
|
||||
peer-checked:border-cta-blue peer-checked:bg-cta-blue
|
||||
class="w-5 h-5 border-2 border-slate-300 dark:border-gray-600 rounded
|
||||
peer-checked:border-cta-blue dark:peer-checked:border-blue-500 peer-checked:bg-cta-blue dark:peer-checked:bg-blue-500
|
||||
transition-all duration-200 ease-in-out
|
||||
flex items-center justify-center
|
||||
bg-slate-50
|
||||
focus-within:ring-2 focus-within:ring-cta-blue focus-within:ring-offset-2"
|
||||
bg-slate-50 dark:bg-gray-700
|
||||
focus-within:ring-2 focus-within:ring-cta-blue dark:focus-within:ring-blue-500 focus-within:ring-offset-2 dark:focus-within:ring-offset-gray-800"
|
||||
>
|
||||
{#if value}
|
||||
<svg class="w-3 h-3 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
||||
@@ -160,7 +160,9 @@
|
||||
</script>
|
||||
|
||||
{#if visible}
|
||||
<div class="fixed top-0 left-0 w-full h-full bg-cta-blue opacity-20 blur-xl" />
|
||||
<div
|
||||
class="fixed top-0 left-0 w-full h-full bg-cta-blue dark:bg-gray-900 opacity-20 blur-xl transition-colors duration-200"
|
||||
/>
|
||||
<div
|
||||
class="fixed top-0 left-0 w-full h-full flex justify-center items-center backdrop-blur-sm z-20"
|
||||
role="dialog"
|
||||
@@ -170,23 +172,34 @@
|
||||
>
|
||||
<section
|
||||
bind:this={confirmElement}
|
||||
class="flex flex-col items-center w-1/3 bg-slate-100 shadow-xl rounded-md"
|
||||
class="flex flex-col items-center w-1/3 bg-slate-100 dark:bg-gray-800 shadow-xl dark:shadow-gray-900/70 rounded-md transition-colors duration-200"
|
||||
>
|
||||
<div class="bg-cta-orange2 text-white rounded-tl-md rounded-tr-md w-full">
|
||||
<div
|
||||
class="bg-cta-orange2 dark:bg-orange-600 text-white rounded-tl-md rounded-tr-md w-full transition-colors duration-200"
|
||||
>
|
||||
<p id="confirm-title" class="uppercase font-bold text-center py">Confirm action</p>
|
||||
</div>
|
||||
<h1 class="uppercase font-bold text-center text-gray-500 text-4xl pt-10">Are you sure?</h1>
|
||||
<p id="confirm-description" class="text-center">{confirm_text}</p>
|
||||
<h1
|
||||
class="uppercase font-bold text-center text-gray-500 dark:text-gray-300 text-4xl pt-10 transition-colors duration-200"
|
||||
>
|
||||
Are you sure?
|
||||
</h1>
|
||||
<p
|
||||
id="confirm-description"
|
||||
class="text-center text-gray-700 dark:text-gray-200 transition-colors duration-200"
|
||||
>
|
||||
{confirm_text}
|
||||
</p>
|
||||
<div class="flex pt-4 pb-8">
|
||||
<button
|
||||
class="mt-6 bg-grayblue-dark w-40 py-2 mr-2 hover:bg-slate-300 text-white font-bold uppercase rounded-md"
|
||||
class="mt-6 bg-grayblue-dark dark:bg-gray-600 w-40 py-2 mr-2 hover:bg-slate-300 dark:hover:bg-gray-500 text-white font-bold uppercase rounded-md transition-colors duration-200"
|
||||
on:click={close}
|
||||
aria-label="Cancel action"
|
||||
>
|
||||
no
|
||||
</button>
|
||||
<button
|
||||
class="mt-6 bg-cta-blue w-56 py-2 hover:bg-blue-500 text-white font-bold uppercase rounded-md"
|
||||
class="mt-6 bg-cta-blue dark:bg-blue-600 w-56 py-2 hover:bg-blue-500 dark:hover:bg-blue-700 text-white font-bold uppercase rounded-md transition-colors duration-200"
|
||||
on:click={confirm}
|
||||
aria-label="Confirm action"
|
||||
>
|
||||
|
||||
@@ -59,7 +59,9 @@
|
||||
class:w-60={labelWidth === 'large'}
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<p class="font-semibold text-slate-600 py-2">
|
||||
<p
|
||||
class="font-semibold text-slate-600 dark:text-gray-300 py-2 transition-colors duration-200"
|
||||
>
|
||||
<slot />
|
||||
</p>
|
||||
{#if toolTipText.length > 0}
|
||||
@@ -68,8 +70,12 @@
|
||||
</ToolTip>
|
||||
{/if}
|
||||
{#if optional === true}
|
||||
<div class="bg-gray-100 ml-2 px-2 rounded-md">
|
||||
<p class="text-slate-600 text-xs">optional</p>
|
||||
<div
|
||||
class="bg-gray-100 dark:bg-gray-700 ml-2 px-2 rounded-md transition-colors duration-200"
|
||||
>
|
||||
<p class="text-slate-600 dark:text-gray-300 text-xs transition-colors duration-200">
|
||||
optional
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -86,7 +92,7 @@
|
||||
{required}
|
||||
{disabled}
|
||||
autocomplete="off"
|
||||
class="rounded-md text-center py-2 pl-2 text-gray-600 border border-transparent focus:outline-none focus:border-solid focus:border-slate-400 focus:bg-gray-100 bg-grayblue-light font-normal"
|
||||
class="rounded-md text-center py-2 pl-2 text-gray-600 dark:text-gray-200 border border-transparent dark:border-gray-600 focus:outline-none focus:border-solid focus:border-slate-400 dark:focus:border-blue-500 focus:bg-gray-100 dark:focus:bg-gray-600 bg-grayblue-light dark:bg-gray-700 font-normal transition-colors duration-200"
|
||||
class:text-left={textAlign == 'left'}
|
||||
class:text-center={textAlign == 'center'}
|
||||
class:text-right={textAlign == 'right'}
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
class:w-60={labelWidth === 'large'}
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<p class="font-semibold text-slate-600 py-2">
|
||||
<p class="font-semibold text-slate-600 dark:text-gray-300 py-2 transition-colors duration-200">
|
||||
<slot />
|
||||
</p>
|
||||
{#if toolTipText.length > 0}
|
||||
@@ -121,8 +121,8 @@
|
||||
</ToolTip>
|
||||
{/if}
|
||||
{#if optional === true}
|
||||
<div class="bg-gray-100 ml-2 px-2 rounded-md">
|
||||
<p class="text-slate-600 text-xs">optional</p>
|
||||
<div class="bg-gray-100 dark:bg-gray-700 ml-2 px-2 rounded-md transition-colors duration-200">
|
||||
<p class="text-slate-600 dark:text-gray-300 text-xs">optional</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -139,7 +139,7 @@
|
||||
{readonly}
|
||||
{required}
|
||||
autocomplete="off"
|
||||
class="w-44 rounded-md py-2 pl-2 text-gray-600 border border-transparent focus:outline-none focus:border-solid focus:border-slate-400 focus:bg-gray-100 bg-grayblue-light font-normal"
|
||||
class="w-44 rounded-md py-2 pl-2 text-gray-600 dark:text-gray-300 border border-transparent focus:outline-none focus:border-solid focus:border-slate-400 dark:focus:border-gray-500 focus:bg-gray-100 dark:focus:bg-gray-600 bg-grayblue-light dark:bg-gray-700 font-normal transition-colors duration-200"
|
||||
class:opacity-90={readonly}
|
||||
/>
|
||||
<input
|
||||
@@ -153,7 +153,8 @@
|
||||
{readonly}
|
||||
{required}
|
||||
autocomplete="off"
|
||||
class="ml-2 rounded-md py-2 pl-2 text-gray-600 text-center border border-transparent focus:outline-none focus:border-solid focus:border-slate-400 focus:bg-gray-100 bg-grayblue-light font-normal"
|
||||
class="ml-2 rounded-md py-2 pl-2 text-gray-600 dark:text-gray-300 text-center border border-transparent focus:outline-none focus:border-solid focus:border-slate-400 dark:focus:border-gray-500 focus:bg-gray-100 dark:focus:bg-gray-600 bg-grayblue-light dark:bg-gray-700 font-normal transition-colors duration-200"
|
||||
class:bg-yellow-200={readonly}
|
||||
class:dark:bg-yellow-700={readonly}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="text-gray-900 dark:text-gray-100 transition-colors duration-200">
|
||||
{#if date}
|
||||
{#if !hideHours}
|
||||
{date.toLocaleString()}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<div>
|
||||
<div>
|
||||
<button
|
||||
class="fixed border-2 border-slate-500 bg-white w-8 h-8 left-2 pb-1 bottom-4 text-white rounded-full hover:right-3 hover:bottom-3 hover:w-10 hover:h-10 transition-all"
|
||||
class="fixed border-2 border-slate-500 dark:border-slate-400 bg-white dark:bg-gray-700 w-8 h-8 left-2 pb-1 bottom-4 text-white rounded-full hover:right-3 hover:bottom-3 hover:w-10 hover:h-10 transition-all"
|
||||
on:click={() => (visible = !visible)}
|
||||
>
|
||||
{#if visible}
|
||||
@@ -41,46 +41,75 @@
|
||||
{#if visible}
|
||||
<div
|
||||
transition:fade={{ duration: 100 }}
|
||||
class="absolute right-0 h-auto bg-black text-white p-4 z-40"
|
||||
class="absolute right-0 h-auto bg-black dark:bg-gray-900 text-white p-4 z-40 border border-gray-600 dark:border-gray-500"
|
||||
>
|
||||
<h1 class="text-xl m-4">Developer Panel</h1>
|
||||
<h2 class="text-lg font-bold m-4">Links</h2>
|
||||
<ul class="m-4">
|
||||
<li>
|
||||
<a href="http://localhost:8101" target="_blank">Database</a>
|
||||
<a
|
||||
href="http://localhost:8101"
|
||||
target="_blank"
|
||||
class="text-blue-400 hover:text-blue-300 underline">Database</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://localhost:8102" target="_blank">Mailbox</a>
|
||||
<a
|
||||
href="http://localhost:8102"
|
||||
target="_blank"
|
||||
class="text-blue-400 hover:text-blue-300 underline">Mailbox</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://localhost:8103" target="_blank">Container logs</a>
|
||||
<a
|
||||
href="http://localhost:8103"
|
||||
target="_blank"
|
||||
class="text-blue-400 hover:text-blue-300 underline">Container logs</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://localhost:8104" target="_blank">Container stats</a>
|
||||
<a
|
||||
href="http://localhost:8104"
|
||||
target="_blank"
|
||||
class="text-blue-400 hover:text-blue-300 underline">Container stats</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
<h2 class="text-lg font-bold m-4">Toast</h2>
|
||||
<ul class="m-4">
|
||||
<li>
|
||||
<button on:click={() => triggerToast('Success')}>Trigger toast - Success</button>
|
||||
<button
|
||||
on:click={() => triggerToast('Success')}
|
||||
class="text-green-400 hover:text-green-300 underline">Trigger toast - Success</button
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<button on:click={() => triggerToast('Info')}>Trigger toast - Info</button>
|
||||
<button
|
||||
on:click={() => triggerToast('Info')}
|
||||
class="text-blue-400 hover:text-blue-300 underline">Trigger toast - Info</button
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<button on:click={() => triggerToast('Warning')}>Trigger toast - Warning</button>
|
||||
<button
|
||||
on:click={() => triggerToast('Warning')}
|
||||
class="text-yellow-400 hover:text-yellow-300 underline"
|
||||
>Trigger toast - Warning</button
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<button on:click={() => triggerToast('Error')}>Trigger toast - Error</button>
|
||||
<button
|
||||
on:click={() => triggerToast('Error')}
|
||||
class="text-red-400 hover:text-red-300 underline">Trigger toast - Error</button
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="pt-4">
|
||||
<h2 class="text-lg font-bold">Global State</h2>
|
||||
<table class="border-2">
|
||||
<table class="border-2 border-gray-400 dark:border-gray-500">
|
||||
{#each Object.entries(state) as [key, value]}
|
||||
<tr class="flex flex-col border-2 border-white">
|
||||
<td class="p-4 font-bold border-1 border-white">{key}</td>
|
||||
<td class="p-4 border-1 border-white w-full">
|
||||
<tr class="flex flex-col border-2 border-white dark:border-gray-600">
|
||||
<td class="p-4 font-bold border-1 border-white dark:border-gray-600">{key}</td>
|
||||
<td class="p-4 border-1 border-white dark:border-gray-600 w-full">
|
||||
{#if typeof value === 'object'}
|
||||
<pre class="whitespace-pre-wrap">{JSON.stringify(value, null, 2)}</pre>
|
||||
{:else}
|
||||
|
||||
@@ -70,7 +70,8 @@
|
||||
.attr('y1', (height - margin.top - margin.bottom) / 2)
|
||||
.attr('y2', (height - margin.top - margin.bottom) / 2)
|
||||
.attr('stroke', '#E2E8F0')
|
||||
.attr('stroke-width', 2);
|
||||
.attr('stroke-width', 2)
|
||||
.attr('class', 'timeline-line');
|
||||
|
||||
// X Axis
|
||||
g.append('g')
|
||||
@@ -340,7 +341,8 @@
|
||||
.attr('y1', (height - margin.top - margin.bottom) / 2)
|
||||
.attr('y2', (height - margin.top - margin.bottom) / 2)
|
||||
.attr('stroke', '#E2E8F0')
|
||||
.attr('stroke-width', 2);
|
||||
.attr('stroke-width', 2)
|
||||
.attr('class', 'timeline-line');
|
||||
|
||||
const ghostDots = 8;
|
||||
const ghostDotsData = Array(ghostDots).fill(null);
|
||||
@@ -355,6 +357,7 @@
|
||||
.attr('r', 6)
|
||||
.attr('fill', '#E2E8F0')
|
||||
.attr('stroke', '#fff')
|
||||
.attr('class', 'ghost-dot-fill')
|
||||
.attr('stroke-width', 2);
|
||||
}
|
||||
|
||||
@@ -378,10 +381,10 @@
|
||||
try {
|
||||
tooltip.innerHTML = `
|
||||
<div class="p-2">
|
||||
<div class="font-semibold text-lg text-gray-700 border-b-2">${toEvent(d.eventName).name}</div>
|
||||
<div class="">${d.recipient?.email ?? ''}</div>
|
||||
<div class="text-xs text-gray-600">${new Date(d.createdAt).toLocaleString()}</div>
|
||||
${d.data ? `<div class="text-xs mt-1">${d.data}</div>` : ''}
|
||||
<div class="font-semibold text-lg text-gray-700 dark:text-gray-200 border-b-2">${toEvent(d.eventName).name}</div>
|
||||
<div class="text-gray-600 dark:text-gray-300">${d.recipient?.email ?? ''}</div>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">${new Date(d.createdAt).toLocaleString()}</div>
|
||||
${d.data ? `<div class="text-xs mt-1 text-gray-500 dark:text-gray-400">${d.data}</div>` : ''}
|
||||
</div>
|
||||
`;
|
||||
} catch (e) {
|
||||
@@ -435,7 +438,10 @@
|
||||
function getEventColor(eventName) {
|
||||
if (!eventName) return '#6B7280'; // Default gray for undefined events
|
||||
|
||||
const colorMap = {
|
||||
const isDark =
|
||||
typeof document !== 'undefined' && document.documentElement.classList.contains('dark');
|
||||
|
||||
const lightModeColors = {
|
||||
campaign_scheduled: '#4e68d8',
|
||||
campaign_active: '#53afe3',
|
||||
campaign_self_managed: '#303f9f',
|
||||
@@ -443,7 +449,7 @@
|
||||
campaign_recipient_scheduled: '#4e68d8',
|
||||
campaign_recipient_message_sent: '#94cae6',
|
||||
campaign_recipient_message_failed: '#f2bb58',
|
||||
campaign_recipient_message_read: '#4cb5b5',
|
||||
campaign_recipient_message_read: '#22c55e', // More muted green
|
||||
campaign_recipient_before_page_visited: '#eea5fa',
|
||||
campaign_recipient_page_visited: '#f96dcf',
|
||||
campaign_recipient_after_page_visited: '#f6287b',
|
||||
@@ -451,6 +457,25 @@
|
||||
campaign_recipient_cancelled: '#161692',
|
||||
default: '#6B7280'
|
||||
};
|
||||
|
||||
const darkModeColors = {
|
||||
campaign_scheduled: '#60a5fa',
|
||||
campaign_active: '#38bdf8',
|
||||
campaign_self_managed: '#6366f1',
|
||||
campaign_closed: '#a1a1aa',
|
||||
campaign_recipient_scheduled: '#60a5fa',
|
||||
campaign_recipient_message_sent: '#7dd3fc',
|
||||
campaign_recipient_message_failed: '#fbbf24',
|
||||
campaign_recipient_message_read: '#4ade80', // Softer green for dark mode
|
||||
campaign_recipient_before_page_visited: '#d8b4fe',
|
||||
campaign_recipient_page_visited: '#f472b6',
|
||||
campaign_recipient_after_page_visited: '#fb7185',
|
||||
campaign_recipient_submitted_data: '#f87171',
|
||||
campaign_recipient_cancelled: '#3730a3',
|
||||
default: '#9ca3af'
|
||||
};
|
||||
|
||||
const colorMap = isDark ? darkModeColors : lightModeColors;
|
||||
return colorMap[eventName] || colorMap.default;
|
||||
}
|
||||
|
||||
@@ -465,11 +490,11 @@
|
||||
bind:this={svg}
|
||||
width="100%"
|
||||
height={height + 20}
|
||||
class="bg-white rounded-lg shadow-sm p-2"
|
||||
class="bg-white dark:bg-gray-800 rounded-lg shadow-sm p-2 transition-colors duration-200"
|
||||
/>
|
||||
<div
|
||||
bind:this={tooltip}
|
||||
class="absolute hidden bg-white shadow-lg rounded-lg border border-gray-200 z-10 pointer-events-none"
|
||||
class="absolute hidden bg-white dark:bg-gray-800 shadow-lg rounded-lg border border-gray-200 dark:border-gray-600 z-10 pointer-events-none transition-colors duration-200"
|
||||
/>
|
||||
{#if !isGhost}
|
||||
<div class="absolute top-2 right-2 flex gap-2">
|
||||
@@ -478,23 +503,23 @@
|
||||
use24Hour = !use24Hour;
|
||||
updateTimeline();
|
||||
}}
|
||||
class="px-2 py-1 text-xs bg-white border border-slate-200 rounded shadow-sm hover:bg-slate-50 text-slate-600"
|
||||
class="px-2 py-1 text-xs bg-white dark:bg-gray-700 border border-slate-200 dark:border-gray-600 rounded shadow-sm hover:bg-slate-50 dark:hover:bg-gray-600 text-slate-600 dark:text-gray-300 transition-colors duration-200"
|
||||
>
|
||||
{use24Hour ? '12h' : '24h'}
|
||||
</button>
|
||||
<button
|
||||
on:click={resetZoom}
|
||||
class="px-2 py-1 text-xs bg-white border border-slate-200 rounded shadow-sm hover:bg-slate-50 text-slate-600"
|
||||
class="px-2 py-1 text-xs bg-white dark:bg-gray-700 border border-slate-200 dark:border-gray-600 rounded shadow-sm hover:bg-slate-50 dark:hover:bg-gray-600 text-slate-600 dark:text-gray-300 transition-colors duration-200"
|
||||
>
|
||||
Reset View
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="absolute top-0 left-1/2 transform -translate-x-1/2 bg-white px-3 py-1 rounded-b-lg shadow-sm border border-t-0 text-sm font-medium text-slate-700"
|
||||
class="absolute top-0 left-1/2 transform -translate-x-1/2 bg-white dark:bg-gray-800 px-3 py-1 rounded-b-lg shadow-sm border border-t-0 border-gray-200 dark:border-gray-600 text-sm font-medium text-slate-700 dark:text-gray-200 transition-colors duration-200"
|
||||
>
|
||||
{currentCenterDate}
|
||||
</div>
|
||||
<div class="absolute bottom-0 right-0 p-2 text-xs text-slate-500">
|
||||
<div class="absolute bottom-0 right-0 p-2 text-xs text-slate-500 dark:text-gray-400">
|
||||
Drag to pan • Scroll to zoom
|
||||
</div>
|
||||
{/if}
|
||||
@@ -507,12 +532,45 @@
|
||||
font-weight: 500;
|
||||
fill: #4a5568;
|
||||
}
|
||||
|
||||
:global(.dark .x-axis text) {
|
||||
fill: #d1d5db;
|
||||
}
|
||||
|
||||
:global(.x-axis line) {
|
||||
stroke: #e2e8f0;
|
||||
}
|
||||
|
||||
:global(.dark .x-axis line) {
|
||||
stroke: #4b5563;
|
||||
}
|
||||
|
||||
:global(.x-axis path) {
|
||||
stroke: #e2e8f0;
|
||||
}
|
||||
|
||||
:global(.dark .x-axis path) {
|
||||
stroke: #4b5563;
|
||||
}
|
||||
|
||||
:global(.timeline-line) {
|
||||
stroke: #e2e8f0;
|
||||
}
|
||||
|
||||
:global(.dark .timeline-line) {
|
||||
stroke: #4b5563;
|
||||
}
|
||||
|
||||
:global(.ghost-dot-fill) {
|
||||
fill: #e2e8f0;
|
||||
stroke: #fff;
|
||||
}
|
||||
|
||||
:global(.dark .ghost-dot-fill) {
|
||||
fill: #4b5563;
|
||||
stroke: #1f2937;
|
||||
}
|
||||
|
||||
:global(.ghost-dot) {
|
||||
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
<label class="flex flex-col py-2 w-56">
|
||||
<div class="flex items-center">
|
||||
<p class="font-semibold text-slate-600 py-2">
|
||||
<p class="font-semibold text-slate-600 dark:text-gray-300 py-2 transition-colors duration-200">
|
||||
<slot />
|
||||
</p>
|
||||
{#if toolTipText.length > 0}
|
||||
@@ -52,8 +52,8 @@
|
||||
</ToolTip>
|
||||
{/if}
|
||||
{#if optional === true}
|
||||
<div class="bg-gray-100 ml-2 px-2 rounded-md">
|
||||
<p class="text-slate-600 text-xs">optional</p>
|
||||
<div class="bg-gray-100 dark:bg-gray-700 ml-2 px-2 rounded-md transition-colors duration-200">
|
||||
<p class="text-slate-600 dark:text-gray-300 text-xs">optional</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -68,6 +68,6 @@
|
||||
{multiple}
|
||||
{required}
|
||||
{placeholder}
|
||||
class="border-solid border-2 py-2 px-2 rounded-md file:px-4 file:py-2 file:text-white file:cursor-pointer file:text-sm file:font-semibold bg-white file:bg-cta-green hover:cursor-pointer file:hover:bg-teal-300 file:border-hidden file:rounded-md"
|
||||
class="border-solid border-2 border-gray-300 dark:border-gray-600 py-2 px-2 rounded-md file:px-4 file:py-2 file:text-white file:cursor-pointer file:text-sm file:font-semibold bg-white dark:bg-gray-700 file:bg-cta-blue hover:cursor-pointer file:hover:bg-blue-600 dark:file:bg-indigo-600 dark:file:hover:bg-indigo-700 file:border-hidden file:rounded-md text-gray-900 dark:text-white transition-colors duration-200"
|
||||
/>
|
||||
</label>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<form
|
||||
on:submit|preventDefault
|
||||
class="flex w-60 flex-col"
|
||||
class="flex w-60 flex-col bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 transition-colors duration-200"
|
||||
class:h-full={fullHeight}
|
||||
class:w-full={fullWidth}
|
||||
bind:this={bindTo}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
class:w-32={size === 'small'}
|
||||
class:w-36={size === 'medium'}
|
||||
class:w-60={size === 'large'}
|
||||
class="bg-cta-blue px-2 py-2 self-end hover:bg-blue-500 text-white text-sm font-bold flex justify-center items-center uppercase rounded-md"
|
||||
class="bg-cta-blue dark:bg-blue-600 px-2 py-2 self-end hover:bg-blue-500 dark:hover:bg-blue-700 text-white text-sm font-bold flex justify-center items-center uppercase rounded-md transition-colors duration-200 disabled:opacity-50 dark:disabled:opacity-60"
|
||||
on:click
|
||||
>
|
||||
{#if isSubmitting}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="px-6 flex flex-col items-start sm:items-start md:items-start lg:items-start xl:items-start 2xl:items-start"
|
||||
class="px-6 flex flex-col items-start sm:items-start md:items-start lg:items-start xl:items-start 2xl:items-start text-gray-900 dark:text-gray-100 transition-colors duration-200"
|
||||
class:overflow-x-scroll={overflowX}
|
||||
>
|
||||
<slot />
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="scrollX col-start-1 col-span-3 row-start-1 row-span-5 py-8 flex-col flex sm:flex-col md:flex-col lg:flex-row justify-center"
|
||||
class="scrollX col-start-1 col-span-3 row-start-1 row-span-5 py-8 flex-col flex sm:flex-col md:flex-col lg:flex-row justify-center bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 transition-colors duration-200"
|
||||
{id}
|
||||
>
|
||||
<slot />
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<div class="flex col-span-12 justify-center p-4">
|
||||
<div class="w-80 flex col-span-12 justify-center">
|
||||
<div
|
||||
class="flex items-center w-full bg-pleasant-gray rounded-md border text-center p-2 font-titilium"
|
||||
class="flex items-center w-full bg-pleasant-gray dark:bg-gray-700 rounded-md border border-gray-200 dark:border-gray-600 text-center p-2 font-titilium transition-colors duration-200"
|
||||
>
|
||||
<svg class="w-12 ml-4" transition:slide viewBox="0 0 32.94 32.94">
|
||||
<circle
|
||||
@@ -33,7 +33,7 @@
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
<p class="pl-4">
|
||||
<p class="pl-4 text-gray-700 dark:text-gray-200 transition-colors duration-200">
|
||||
{message}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<form
|
||||
on:submit|preventDefault
|
||||
class="col-start-1 col-span-3 row-start-1 row-span-5 py-8 flex-col flex sm:flex-col md:flex-col lg:flex-row justify-start"
|
||||
class="col-start-1 col-span-3 row-start-1 row-span-5 py-8 flex-col flex sm:flex-col md:flex-col lg:flex-row justify-start bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 transition-colors duration-200"
|
||||
bind:this={bindTo}
|
||||
>
|
||||
<slot />
|
||||
|
||||
@@ -19,12 +19,12 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="py-4 row-span-2 col-start-1 col-span-3 border-t-2 w-full flex flex-row justify-center items-center sm:justify-center md:justify-center lg:justify-end xl:justify-end 2xl:justify-end"
|
||||
class="py-4 row-span-2 col-start-1 col-span-3 border-t-2 border-gray-200 dark:border-gray-700 w-full flex flex-row justify-center items-center sm:justify-center md:justify-center lg:justify-end xl:justify-end 2xl:justify-end bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 transition-colors duration-200"
|
||||
>
|
||||
<button
|
||||
type="reset"
|
||||
on:click|preventDefault={onCloseModal}
|
||||
class="bg-slate-400 hover:bg-slate-300 text-sm mr-2 uppercase font-bold px-4 py-2 text-white rounded-md"
|
||||
class="bg-slate-400 hover:bg-slate-300 dark:bg-slate-600 dark:hover:bg-slate-500 text-sm mr-2 uppercase font-bold px-4 py-2 text-white rounded-md transition-colors duration-200"
|
||||
>{closeText}</button
|
||||
>
|
||||
<FormButton {isSubmitting}>{okText}</FormButton>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<form
|
||||
on:submit|preventDefault
|
||||
inert={isSubmitting}
|
||||
class="grid grid-cols-3 grid-rows-1 w-full h-full flex-col"
|
||||
class="grid grid-cols-3 grid-rows-1 w-full h-full flex-col bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 transition-colors duration-200"
|
||||
class:opacity-70={isSubmitting}
|
||||
bind:this={bindTo}
|
||||
{novalidate}
|
||||
|
||||
@@ -4,18 +4,18 @@
|
||||
const widths = ['w-16', 'w-20', 'w-24'];
|
||||
let height = 'h-4';
|
||||
let width = widths[Math.floor(Math.random() * widths.length)];
|
||||
let gradient = 'from-gray-300 to-transparent bg-gradient-to-r';
|
||||
let gradient = 'from-gray-300 dark:from-gray-600 to-transparent bg-gradient-to-r';
|
||||
if (square) {
|
||||
width = 'h-4';
|
||||
height = 'w-4';
|
||||
gradient = 'bg-gray-300';
|
||||
gradient = 'bg-gray-300 dark:bg-gray-600';
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if center}
|
||||
<div class="flex items-center justify-center">
|
||||
<div class="{width} {height} {gradient} "> </div>
|
||||
<div class="{width} {height} {gradient} transition-colors duration-200"> </div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="{width} {height} {gradient}"> </div>
|
||||
<div class="{width} {height} {gradient} transition-colors duration-200"> </div>
|
||||
{/if}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<div class="bg-gray-300 w-full h-1/10 flex justify-center">
|
||||
<img alt="logo" src="/logo-white.svg" class="w-1/3 sm:w-1/4 lg:w-2/10 xl:w-1/10 2xl:w-1/10" />
|
||||
</div>
|
||||
<div
|
||||
class="bg-gray-300 dark:bg-gray-700 w-full h-1/10 flex justify-center transition-colors duration-200"
|
||||
>
|
||||
<img alt="logo" src="/logo-white.svg" class="w-1/3 sm:w-1/4 lg:w-2/10 xl:w-1/10 2xl:w-1/10" />
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<h1 class="text-3xl text-gray-600 font-bold uppercase">
|
||||
<h1
|
||||
class="text-3xl text-gray-600 dark:text-gray-300 font-bold uppercase transition-colors duration-200"
|
||||
>
|
||||
<slot />
|
||||
</h1>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script>
|
||||
export let name = 'World';
|
||||
console.log(name)
|
||||
console.log(name);
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<main class="text-gray-900 dark:text-gray-100 transition-colors duration-200">
|
||||
Hello {name}!
|
||||
</main>
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col w-full p-4 h-24">
|
||||
<label for={fieldName} class="text-md font-semibold font-titilium text-pc-darkblue"
|
||||
<label
|
||||
for={fieldName}
|
||||
class="text-md font-semibold font-titilium text-pc-darkblue dark:text-gray-200"
|
||||
>{fieldName}</label
|
||||
>
|
||||
<input
|
||||
@@ -32,6 +34,6 @@
|
||||
{type}
|
||||
id={fieldName}
|
||||
name={fieldName}
|
||||
class="w-full p-2 rounded bg-pc-lightblue focus:outline-none focus:ring-0 focus:border-cta-blue focus:border-2"
|
||||
class="w-full p-2 rounded bg-pc-lightblue dark:bg-gray-700 dark:border-gray-600 dark:text-white dark:placeholder-gray-400 focus:outline-none focus:ring-0 focus:border-cta-blue dark:focus:border-blue-500 focus:border-2 transition-colors duration-200"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -15,13 +15,16 @@
|
||||
</script>
|
||||
|
||||
{#if $isLoading && isAnimating}
|
||||
<div class="fixed top-0 left-0 w-full h-full opacity-[0.5]" transition:blur={{ duration }} />
|
||||
<div
|
||||
class="fixed top-0 left-0 w-full h-full bg-black dark:bg-gray-900 opacity-[0.5] transition-colors duration-200"
|
||||
transition:blur={{ duration }}
|
||||
/>
|
||||
<div
|
||||
transition:blur={{ duration }}
|
||||
class="fixed top-0 left-0 w-full h-full flex justify-center items-center backdrop-blur-sm z-50"
|
||||
>
|
||||
<div
|
||||
class="w-20 h-20 border-t-8 border-t-cta-blue border-r-8 border-r-cta-blue border-b-cta-blue border-b-8 border-l-transparent border-l-8 rounded-full animate-spin"
|
||||
class="w-20 h-20 border-t-8 border-t-cta-blue dark:border-t-blue-500 border-r-8 border-r-cta-blue dark:border-r-blue-500 border-b-cta-blue dark:border-b-blue-500 border-b-8 border-l-transparent border-l-8 rounded-full animate-spin transition-colors duration-200"
|
||||
></div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -240,7 +240,9 @@
|
||||
|
||||
{#if visible}
|
||||
<div bind:this={bindTo}>
|
||||
<div class="fixed top-0 left-0 w-full h-full bg-cta-blue opacity-20 blur-xl" />
|
||||
<div
|
||||
class="fixed top-0 left-0 w-full h-full bg-cta-blue dark:bg-gray-900 opacity-20 blur-xl transition-colors duration-200"
|
||||
/>
|
||||
<div
|
||||
class="fixed top-0 left-0 w-full h-full flex justify-center items-center backdrop-blur-sm z-20"
|
||||
role="dialog"
|
||||
@@ -250,11 +252,11 @@
|
||||
>
|
||||
<section
|
||||
bind:this={modalElement}
|
||||
class="shadow-xl w-auto ml-20 mr-8 max-h-[90vh] bg-white opacity-100 rounded-md flex flex-col"
|
||||
class="shadow-xl dark:shadow-gray-900/70 w-auto ml-20 mr-8 max-h-[90vh] bg-white dark:bg-gray-800 opacity-100 rounded-md flex flex-col transition-colors duration-200"
|
||||
>
|
||||
<div
|
||||
class:opacity-20={isSubmitting}
|
||||
class="bg-cta-blue text-white rounded-t-md py-4 px-8 flex justify-between flex-shrink-0"
|
||||
class="bg-cta-blue dark:bg-blue-700 text-white rounded-t-md py-4 px-8 flex justify-between flex-shrink-0 transition-colors duration-200"
|
||||
>
|
||||
<div class="flex-1">
|
||||
<h1 id="modal-title" class="uppercase mr-8 font-semibold text-2xl">{headerText}</h1>
|
||||
|
||||
@@ -43,12 +43,16 @@
|
||||
|
||||
<div class="flex items-center mb-8 mt-4">
|
||||
<button
|
||||
class="bg-highlight-blue w-8 text-white hover:bg-active-blue m-1 rounded-md py-1 px-1"
|
||||
class="bg-highlight-blue dark:bg-blue-600 w-8 text-white hover:bg-active-blue dark:hover:bg-blue-700 m-1 rounded-md py-1 px-1 transition-colors duration-200"
|
||||
on:click|preventDefault={previousPage}><<</button
|
||||
>
|
||||
<div class="w-8 text-center bg-grayblue-light rounded-md py-1 px-1">{currentPage}</div>
|
||||
<div
|
||||
class="w-8 text-center bg-grayblue-light dark:bg-gray-700 text-gray-700 dark:text-gray-200 rounded-md py-1 px-1 transition-colors duration-200"
|
||||
>
|
||||
{currentPage}
|
||||
</div>
|
||||
<button
|
||||
class="bg-highlight-blue w-8 text-white hover:bg-active-blue m-1 rounded-md py-1 px-1"
|
||||
class="bg-highlight-blue dark:bg-blue-600 w-8 text-white hover:bg-active-blue dark:hover:bg-blue-700 m-1 rounded-md py-1 px-1 transition-colors duration-200"
|
||||
on:click|preventDefault={nextPage}>>></button
|
||||
>
|
||||
</div>
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
|
||||
<label class="flex flex-col py-2 w-60">
|
||||
<div class="flex items-center">
|
||||
<p class="font-semibold text-slate-600 py-2">
|
||||
<p class="font-semibold text-slate-600 dark:text-gray-300 py-2 transition-colors duration-200">
|
||||
<slot />
|
||||
</p>
|
||||
{#if toolTipText.length > 0}
|
||||
@@ -68,8 +68,10 @@
|
||||
</ToolTip>
|
||||
{/if}
|
||||
{#if optional === true}
|
||||
<div class="bg-gray-100 ml-2 px-2 rounded-md">
|
||||
<p class="text-slate-600 text-xs">optional</p>
|
||||
<div class="bg-gray-100 dark:bg-gray-700 ml-2 px-2 rounded-md transition-colors duration-200">
|
||||
<p class="text-slate-600 dark:text-gray-300 text-xs transition-colors duration-200">
|
||||
optional
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -86,7 +88,7 @@
|
||||
minlength={minLength}
|
||||
maxlength={maxLength}
|
||||
{required}
|
||||
class="text-ellipsis w-60 rounded-md py-2 pl-4 text-gray-600 border border-transparent focus:outline-none focus:border-solid focus:border-slate-400 focus:bg-gray-100 bg-grayblue-light font-normal"
|
||||
class="text-ellipsis w-60 rounded-md py-2 pl-4 pr-12 text-gray-600 dark:text-gray-200 border border-transparent dark:border-gray-600 focus:outline-none focus:border-solid focus:border-slate-400 dark:focus:border-blue-500 focus:bg-gray-100 dark:focus:bg-gray-600 bg-grayblue-light dark:bg-gray-700 font-normal transition-colors duration-200"
|
||||
/>
|
||||
{:else}
|
||||
<input
|
||||
@@ -100,18 +102,26 @@
|
||||
{placeholder}
|
||||
autocomplete="off"
|
||||
{required}
|
||||
class="text-ellipsis w-60 rounded-md py-2 pl-4 text-gray-600 border border-transparent focus:outline-none focus:border-solid focus:border-slate-400 focus:bg-gray-100 bg-grayblue-light font-normal"
|
||||
class="text-ellipsis w-60 rounded-md py-2 pl-4 pr-12 text-gray-600 dark:text-gray-200 border border-transparent dark:border-gray-600 focus:outline-none focus:border-solid focus:border-slate-400 dark:focus:border-blue-500 focus:bg-gray-100 dark:focus:bg-gray-600 bg-grayblue-light dark:bg-gray-700 font-normal transition-colors duration-200"
|
||||
/>
|
||||
{/if}
|
||||
<button
|
||||
class="absolute w-8 mr-2 hover:opacity-70"
|
||||
class="absolute w-8 mr-2 hover:opacity-70 transition-opacity duration-200"
|
||||
on:click={handleClick}
|
||||
on:keyup={handleClick}
|
||||
>
|
||||
{#if viewPassword}
|
||||
<img src="/view.svg" alt="view" />
|
||||
<img
|
||||
src="/view.svg"
|
||||
alt="view"
|
||||
class="dark:filter dark:brightness-0 dark:invert transition-all duration-200"
|
||||
/>
|
||||
{:else}
|
||||
<img src="/toggle-view.svg" alt="toggle view" />
|
||||
<img
|
||||
src="/toggle-view.svg"
|
||||
alt="toggle view"
|
||||
class="dark:filter dark:brightness-0 dark:invert transition-all duration-200"
|
||||
/>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -88,6 +88,7 @@
|
||||
: value instanceof Date
|
||||
? value.toLocaleString()
|
||||
: ''}
|
||||
class="text-gray-600 dark:text-gray-400 transition-colors duration-200"
|
||||
>
|
||||
{formattedTime}
|
||||
</span>
|
||||
|
||||
@@ -30,4 +30,6 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="w-full h-full flex border-4 border-pc-darkblue animate-pulse"></div>
|
||||
<div
|
||||
class="w-full h-full flex border-4 border-pc-darkblue dark:border-gray-600 animate-pulse transition-colors duration-200"
|
||||
></div>
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
setSearch();
|
||||
}
|
||||
}}
|
||||
class="bg-grayblue-light w-56 border text-gray-600 border-gray-300 pl-8 py-2 relative rounded-lg focus:outline-none focus:ring-0 focus:border-cta-blue focus:border"
|
||||
class="bg-grayblue-light dark:bg-gray-700 w-56 border text-gray-600 dark:text-gray-200 border-gray-300 dark:border-gray-600 pl-8 py-2 relative rounded-lg focus:outline-none focus:ring-0 focus:border-cta-blue dark:focus:border-blue-500 focus:border transition-colors duration-200 dark:placeholder-gray-400"
|
||||
placeholder="Search"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -21,10 +21,12 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div >
|
||||
<label for="pet-select">Show:</label>
|
||||
<div>
|
||||
<label for="pet-select" class="text-gray-600 dark:text-gray-300 transition-colors duration-200"
|
||||
>Show:</label
|
||||
>
|
||||
<select
|
||||
class="bg-grayblue-light px-2 py-1 rounded-md text-gray-600 border border-transparent focus:outline-none focus:border-solid focus:border focus:border-slate-400 focus:bg-gray-100"
|
||||
class="bg-grayblue-light dark:bg-gray-700 px-2 py-1 rounded-md text-gray-600 dark:text-gray-200 border border-transparent dark:border-gray-600 focus:outline-none focus:border-solid focus:border focus:border-slate-400 dark:focus:border-blue-500 focus:bg-gray-100 dark:focus:bg-gray-600 transition-colors duration-200"
|
||||
name="entries"
|
||||
id="entries"
|
||||
bind:value
|
||||
|
||||
@@ -15,15 +15,19 @@
|
||||
<div class="flex flex-col gap-2 py-2">
|
||||
{#if label}
|
||||
<div class="flex flex-row">
|
||||
<div class="font-semibold text-slate-600">{label}</div>
|
||||
<div class="font-semibold text-slate-600 dark:text-gray-300 transition-colors duration-200">
|
||||
{label}
|
||||
</div>
|
||||
{#if toolTipText.length > 0}
|
||||
<ToolTip>
|
||||
{toolTipText}
|
||||
</ToolTip>
|
||||
{/if}
|
||||
{#if optional}
|
||||
<div class="bg-gray-100 ml-2 px-2 rounded-md">
|
||||
<p class="text-slate-600 text-xs">optional</p>
|
||||
<div
|
||||
class="bg-gray-100 dark:bg-gray-700 ml-2 px-2 rounded-md transition-colors duration-200"
|
||||
>
|
||||
<p class="text-slate-600 dark:text-gray-300 text-xs">optional</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -42,11 +46,11 @@
|
||||
p-3 rounded-lg border-2 transition-all duration-200
|
||||
flex flex-col items-center justify-center text-center
|
||||
w-40
|
||||
hover:border-blue-300 hover:bg-blue-50
|
||||
hover:border-blue-300 dark:hover:border-blue-500 hover:bg-blue-50 dark:hover:bg-blue-900/30
|
||||
${
|
||||
value === option.value
|
||||
? 'border-green-500 bg-green-50 text-green-700 '
|
||||
: 'border-gray-200 bg-white text-gray-700 '
|
||||
? 'border-green-500 dark:border-green-400 bg-green-50 dark:bg-green-900/30 text-green-700 dark:text-green-300'
|
||||
: 'border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-200'
|
||||
}
|
||||
`}
|
||||
on:click={() => {
|
||||
@@ -59,7 +63,9 @@
|
||||
{/if}
|
||||
<span class="font-medium text-sm">{option.label}</span>
|
||||
{#if option.description}
|
||||
<span class="text-xs text-gray-500 mt-1">{option.description}</span>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400 mt-1 transition-colors duration-200"
|
||||
>{option.description}</span
|
||||
>
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
|
||||
@@ -43,12 +43,20 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="bg-white p-6 rounded-lg shadow-md border-l-[12px] {borderColor} hover:shadow-lg transition-shadow"
|
||||
class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md dark:shadow-gray-900/50 border-l-[12px] {borderColor} hover:shadow-lg dark:hover:shadow-gray-900/70 transition-all duration-200"
|
||||
>
|
||||
<div class="text-grayblue-dark text-sm font-semibold">{title}</div>
|
||||
<div
|
||||
class="text-grayblue-dark dark:text-gray-400 text-sm font-semibold transition-colors duration-200"
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<span class="text-3xl font-bold text-pc-darkblue {flash ? 'flash' : ''}">
|
||||
<span
|
||||
class="text-3xl font-bold text-pc-darkblue dark:text-gray-100 transition-colors duration-200 {flash
|
||||
? 'flash'
|
||||
: ''}"
|
||||
>
|
||||
{Math.floor($displayValue)}
|
||||
</span>
|
||||
<div class="ml-2">
|
||||
@@ -74,12 +82,14 @@
|
||||
|
||||
{#if validPercentages.length > 0}
|
||||
<button
|
||||
class="mt-2 text-sm text-gray-600 flex items-center"
|
||||
class="mt-2 text-sm text-gray-600 dark:text-gray-400 flex items-center transition-colors duration-200"
|
||||
on:click={cyclePercentage}
|
||||
class:cursor-pointer={validPercentages.length > 1}
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<span class="text-pc-darkblue font-semibold">
|
||||
<span
|
||||
class="text-pc-darkblue dark:text-gray-200 font-semibold transition-colors duration-200"
|
||||
>
|
||||
{validPercentages[currentPercentageIndex].value}%
|
||||
</span>
|
||||
<span class="ml-1">
|
||||
@@ -89,7 +99,7 @@
|
||||
{#if validPercentages.length > 1}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4 ml-1 text-gray-400"
|
||||
class="h-4 w-4 ml-1 text-gray-400 dark:text-gray-500 transition-colors duration-200"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<h2 class="text-lg text-gray-600 font-bold uppercase">
|
||||
<h2
|
||||
class="text-lg text-gray-600 dark:text-gray-300 font-bold uppercase transition-colors duration-200"
|
||||
>
|
||||
<slot />
|
||||
</h2>
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
<span title="Test / Preview" class="select-none px-1 border-2 relative -top-1 rounded-lg text-xs">
|
||||
<span
|
||||
title="Test / Preview"
|
||||
class="select-none px-1 border-2 border-gray-400 dark:border-gray-500 relative -top-1 rounded-lg text-xs text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 transition-colors duration-200"
|
||||
>
|
||||
test
|
||||
</span>
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
|
||||
<label class="flex flex-col py-2">
|
||||
<div class="flex items-center">
|
||||
<p class="font-semibold text-slate-600 py-2">
|
||||
<p class="font-semibold text-slate-600 dark:text-gray-300 py-2 transition-colors duration-200">
|
||||
<slot />
|
||||
</p>
|
||||
{#if toolTipText.length > 0}
|
||||
@@ -68,8 +68,10 @@
|
||||
</ToolTip>
|
||||
{/if}
|
||||
{#if optional === true}
|
||||
<div class="bg-gray-100 ml-2 px-2 rounded-md">
|
||||
<p class="text-slate-600 text-xs">optional</p>
|
||||
<div class="bg-gray-100 dark:bg-gray-700 ml-2 px-2 rounded-md transition-colors duration-200">
|
||||
<p class="text-slate-600 dark:text-gray-300 text-xs transition-colors duration-200">
|
||||
optional
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -93,7 +95,7 @@
|
||||
{required}
|
||||
{placeholder}
|
||||
{pattern}
|
||||
class="text-ellipsis row-start-1 row-span-3 justify-self-center rounded-md py-2 pl-2 text-gray-600 border border-transparent focus:outline-none focus:border-solid focus:border-slate-400 focus:bg-gray-100 bg-grayblue-light font-normal"
|
||||
class="text-ellipsis row-start-1 row-span-3 justify-self-center rounded-md py-2 pl-2 text-gray-600 dark:text-gray-200 border border-transparent dark:border-gray-600 focus:outline-none focus:border-solid focus:border-slate-400 dark:focus:border-blue-500 focus:bg-gray-100 dark:focus:bg-gray-600 bg-grayblue-light dark:bg-gray-700 font-normal transition-colors duration-200"
|
||||
class:w-24={width === 'small'}
|
||||
class:w-60={width === 'medium'}
|
||||
class:w-95={width === 'large'}
|
||||
|
||||
@@ -117,7 +117,9 @@
|
||||
<div class="flex flex-col justify-start">
|
||||
<label class="flex flex-col py-2 relative">
|
||||
<div class="flex items-center">
|
||||
<p class="font-semibold text-slate-600 py-2">
|
||||
<p
|
||||
class="font-semibold text-slate-600 dark:text-gray-300 py-2 transition-colors duration-200"
|
||||
>
|
||||
<slot />
|
||||
</p>
|
||||
{#if toolTipText.length > 0}
|
||||
@@ -126,8 +128,10 @@
|
||||
</ToolTip>
|
||||
{/if}
|
||||
{#if optional === true}
|
||||
<div class="bg-gray-100 ml-2 px-2 rounded-md">
|
||||
<p class="text-slate-600 text-xs">optional</p>
|
||||
<div
|
||||
class="bg-gray-100 dark:bg-gray-700 ml-2 px-2 rounded-md transition-colors duration-200"
|
||||
>
|
||||
<p class="text-slate-600 dark:text-gray-300 text-xs">optional</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -151,7 +155,7 @@
|
||||
{id}
|
||||
required={required && !value.length}
|
||||
autocomplete="off"
|
||||
class="w-full relative rounded-md py-2 pl-4 focus:pl-10 text-gray-600 border border-transparent focus:outline-none focus:border-solid focus:border focus:border-slate-400 focus:bg-gray-100 bg-grayblue-light font-normal cursor-pointer focus:cursor-text"
|
||||
class="w-full relative rounded-md py-2 pl-4 focus:pl-10 text-gray-600 dark:text-gray-300 border border-transparent focus:outline-none focus:border-solid focus:border focus:border-slate-400 dark:focus:border-gray-500 focus:bg-gray-100 dark:focus:bg-gray-600 bg-grayblue-light dark:bg-gray-700 font-normal cursor-pointer focus:cursor-text transition-colors duration-200"
|
||||
/>
|
||||
{#if showSelection}
|
||||
<img
|
||||
@@ -165,13 +169,13 @@
|
||||
{#if showSelection}
|
||||
<div class="w-60 absolute top-10 z-50">
|
||||
<ul
|
||||
class="bg-gray-100 list-none mt-4 rounded-md min-w-fit shadow-md border max-h-40 overflow-y-scroll"
|
||||
class="bg-gray-100 dark:bg-gray-700 list-none mt-4 rounded-md min-w-fit shadow-md border border-gray-200 dark:border-gray-600 max-h-40 overflow-y-scroll transition-colors duration-200"
|
||||
>
|
||||
{#if options.length}
|
||||
{#each filteredOptions as option}
|
||||
<li>
|
||||
<button
|
||||
class="w-full text-left bg-slate-100 rounded-md text-gray-600 hover:bg-grayblue-dark hover:text-white py-2 px-2 cursor-pointer"
|
||||
class="w-full text-left bg-slate-100 dark:bg-gray-600 rounded-md text-gray-600 dark:text-gray-200 hover:bg-grayblue-dark dark:hover:bg-gray-500 hover:text-white py-2 px-2 cursor-pointer transition-colors duration-200"
|
||||
on:click={() => {
|
||||
onClickSelectedOption(option);
|
||||
}}
|
||||
@@ -181,7 +185,11 @@
|
||||
</li>
|
||||
{/each}
|
||||
{:else}
|
||||
<li class="w-full bg-slate-100 rounded-md text-gray-600 py-2 px-2">List is empty</li>
|
||||
<li
|
||||
class="w-full bg-slate-100 dark:bg-gray-600 rounded-md text-gray-600 dark:text-gray-300 py-2 px-2 transition-colors duration-200"
|
||||
>
|
||||
List is empty
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
</div>
|
||||
@@ -192,7 +200,7 @@
|
||||
on:click|preventDefault={removeSelection}
|
||||
on:keypress|preventDefault={removeSelection}
|
||||
data-value={option}
|
||||
class="flex flex-row items-center bg-gray-100 hover:bg-gray-200 px-2 py-2 mt-2 mr-2 rounded-md"
|
||||
class="flex flex-row items-center bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 px-2 py-2 mt-2 mr-2 rounded-md text-gray-900 dark:text-gray-100 transition-colors duration-200"
|
||||
>
|
||||
{option}
|
||||
<img class="w-4 ml-2 pointer-events-none" src="/delete2.svg" alt="delete" />
|
||||
|
||||
@@ -71,7 +71,9 @@
|
||||
<div class="flex flex-col justify-start">
|
||||
<label class="flex flex-col py-2 relative">
|
||||
<div class="flex items-center">
|
||||
<p class="font-semibold text-slate-600 py-2">
|
||||
<p
|
||||
class="font-semibold text-slate-600 dark:text-gray-300 py-2 transition-colors duration-200"
|
||||
>
|
||||
<slot />
|
||||
</p>
|
||||
{#if toolTipText.length > 0}
|
||||
@@ -97,7 +99,7 @@
|
||||
on:keyup={_onKeyUp}
|
||||
on:click|stopPropagation={() => {}}
|
||||
autocomplete="off"
|
||||
class="w-full relative rounded-md py-2 pl-4 focus:pl-10 text-gray-600 border border-transparent focus:outline-none focus:border-solid focus:border focus:border-slate-400 focus:bg-gray-100 bg-grayblue-light font-normal cursor-pointer focus:cursor-text"
|
||||
class="w-full relative rounded-md py-2 pl-4 focus:pl-10 text-gray-600 dark:text-gray-100 border border-transparent focus:outline-none focus:border-solid focus:border focus:border-slate-400 dark:focus:border-gray-500 focus:bg-gray-100 dark:focus:bg-gray-700 bg-grayblue-light dark:bg-gray-600 font-normal cursor-pointer focus:cursor-text transition-colors duration-200"
|
||||
{id}
|
||||
{required}
|
||||
/>
|
||||
@@ -109,12 +111,12 @@
|
||||
{#if options.length && showSelection}
|
||||
<div class="w-96 absolute top-10 z-50">
|
||||
<ul
|
||||
class="bg-gray-100 list-none mt-4 rounded-md min-w-fit shadow-md border max-h-40 overflow-y-scroll"
|
||||
class="bg-gray-100 dark:bg-gray-700 list-none mt-4 rounded-md min-w-fit shadow-md dark:shadow-gray-900/50 border border-gray-200 dark:border-gray-600 max-h-40 overflow-y-scroll transition-colors duration-200"
|
||||
>
|
||||
{#each options as option}
|
||||
<li class="break-words">
|
||||
<button
|
||||
class="w-full text-left bg-slate-100 rounded-md text-gray-600 hover:bg-grayblue-dark hover:text-white py-2 px-2 cursor-pointer"
|
||||
class="w-full text-left bg-slate-100 dark:bg-gray-700 rounded-md text-gray-600 dark:text-gray-200 hover:bg-grayblue-dark dark:hover:bg-gray-600 hover:text-white py-2 px-2 cursor-pointer transition-colors duration-200"
|
||||
on:click|preventDefault={() => {
|
||||
value = '';
|
||||
showSelection = false;
|
||||
|
||||
@@ -223,7 +223,10 @@
|
||||
>
|
||||
<label class="flex flex-col py-2 relative" class:py-2={!inline} class:pr-2={inline}>
|
||||
<div class="flex items-center">
|
||||
<p id={labelId} class="font-semibold text-slate-600 py-1">
|
||||
<p
|
||||
id={labelId}
|
||||
class="font-semibold text-slate-600 dark:text-gray-300 py-1 transition-colors duration-200"
|
||||
>
|
||||
<slot />
|
||||
</p>
|
||||
{#if toolTipText.length > 0}
|
||||
@@ -232,8 +235,10 @@
|
||||
</ToolTip>
|
||||
{/if}
|
||||
{#if optional === true}
|
||||
<div class="bg-gray-100 ml-2 px-2 rounded-md">
|
||||
<p class="text-slate-600 text-xs">optional</p>
|
||||
<div
|
||||
class="bg-gray-100 dark:bg-gray-700 ml-2 px-2 rounded-md transition-colors duration-200"
|
||||
>
|
||||
<p class="text-slate-600 dark:text-gray-300 text-xs">optional</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -261,7 +266,7 @@
|
||||
on:keydown={handleKeyDown}
|
||||
on:click={handleFocus}
|
||||
autocomplete="off"
|
||||
class="w-full relative rounded-md py-2 pr-10 text-gray-600 border border-transparent focus:outline-none focus:border-solid focus:border focus:border-slate-400 focus:bg-gray-100 bg-grayblue-light font-normal cursor-pointer focus:cursor-text"
|
||||
class="w-full relative rounded-md py-2 pr-10 text-gray-600 dark:text-gray-300 border border-transparent focus:outline-none focus:border-solid focus:border focus:border-slate-400 dark:focus:border-gray-500 focus:bg-gray-100 dark:focus:bg-gray-600 bg-grayblue-light dark:bg-gray-700 font-normal cursor-pointer focus:cursor-text transition-colors duration-200"
|
||||
class:pl-10={showDropdown}
|
||||
class:pl-4={!showDropdown}
|
||||
class:text-gray-400={!hasValue && !showDropdown}
|
||||
@@ -314,7 +319,7 @@
|
||||
id={listboxId}
|
||||
role="listbox"
|
||||
aria-labelledby={labelId}
|
||||
class="bg-gray-100 list-none mt-4 z-[999] rounded-md min-w-fit shadow-md border max-h-40 overflow-y-scroll"
|
||||
class="bg-gray-100 dark:bg-gray-700 list-none mt-4 z-[999] rounded-md min-w-fit shadow-md border border-gray-200 dark:border-gray-600 max-h-40 overflow-y-scroll transition-colors duration-200"
|
||||
>
|
||||
{#if allOptions.length}
|
||||
{#each allOptions as option, index}
|
||||
@@ -323,7 +328,7 @@
|
||||
id="{listboxId}-option-{index}"
|
||||
role="option"
|
||||
aria-selected={value === option}
|
||||
class="w-full text-left bg-slate-100 rounded-md text-gray-600 hover:bg-grayblue-dark hover:text-white py-2 px-2 cursor-pointer focus:bg-grayblue-dark focus:text-white focus:outline-none"
|
||||
class="w-full text-left bg-slate-100 dark:bg-gray-600 rounded-md text-gray-600 dark:text-gray-200 hover:bg-grayblue-dark dark:hover:bg-gray-500 hover:text-white py-2 px-2 cursor-pointer focus:bg-grayblue-dark dark:focus:bg-gray-500 focus:text-white focus:outline-none transition-colors duration-200"
|
||||
on:click={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@@ -337,7 +342,10 @@
|
||||
</li>
|
||||
{/each}
|
||||
{:else}
|
||||
<li role="none" class="w-full bg-slate-100 rounded-md text-gray-600 py-2 px-2">
|
||||
<li
|
||||
role="none"
|
||||
class="w-full bg-slate-100 dark:bg-gray-600 rounded-md text-gray-600 dark:text-gray-300 py-2 px-2 transition-colors duration-200"
|
||||
>
|
||||
No options available
|
||||
</li>
|
||||
{/if}
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
|
||||
<label class="flex flex-col py-2 w-60" class:w-full={fullWidth}>
|
||||
<div class="flex items-center">
|
||||
<p class="font-bold text-slate-600 py-2">
|
||||
<p class="font-bold text-slate-600 dark:text-gray-300 py-2 transition-colors duration-200">
|
||||
<slot />
|
||||
</p>
|
||||
{#if toolTipText.length > 0}
|
||||
@@ -61,8 +61,10 @@
|
||||
</ToolTip>
|
||||
{/if}
|
||||
{#if optional === true}
|
||||
<div class="bg-gray-100 ml-2 px-2 rounded-md">
|
||||
<p class="text-slate-600 text-xs">optional</p>
|
||||
<div class="bg-gray-100 dark:bg-gray-700 ml-2 px-2 rounded-md transition-colors duration-200">
|
||||
<p class="text-slate-600 dark:text-gray-300 text-xs transition-colors duration-200">
|
||||
optional
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -76,7 +78,7 @@
|
||||
maxlength={maxLength}
|
||||
{readonly}
|
||||
{placeholder}
|
||||
class=" focus:outline-none pl-2 border border-transparent rounded-md focus:border-solid text-gray-600 focus:bg-gray-100 font-light focus:border-slate-400 bg-grayblue-light"
|
||||
class=" focus:outline-none pl-2 border border-transparent dark:border-gray-600 rounded-md focus:border-solid text-gray-600 dark:text-gray-200 focus:bg-gray-100 dark:focus:bg-gray-600 font-light focus:border-slate-400 dark:focus:border-blue-500 bg-grayblue-light dark:bg-gray-700 transition-colors duration-200"
|
||||
class:h-16={height === 'small'}
|
||||
class:h-28={height === 'medium'}
|
||||
class:h-48={height === 'large'}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<script>
|
||||
import { theme, toggleMode, modeLight, modeDark } from '$lib/theme.js';
|
||||
|
||||
// reactive statement to determine current theme
|
||||
$: isDark = $theme === modeDark;
|
||||
|
||||
const handleToggle = () => {
|
||||
toggleMode();
|
||||
};
|
||||
</script>
|
||||
|
||||
<button
|
||||
on:click={handleToggle}
|
||||
class="relative inline-flex items-center justify-center w-6 h-6 rounded transition-colors duration-200 focus:outline-none"
|
||||
title={isDark ? 'Switch to light mode' : 'Switch to dark mode'}
|
||||
aria-label={isDark ? 'Switch to light mode' : 'Switch to dark mode'}
|
||||
>
|
||||
<!-- sun icon for light mode -->
|
||||
<svg
|
||||
class="w-4 h-4 text-yellow-400 transition-all duration-200 {isDark
|
||||
? 'opacity-0 rotate-90 scale-0'
|
||||
: 'opacity-100 rotate-0 scale-100'} absolute"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<!-- moon icon for dark mode -->
|
||||
<svg
|
||||
class="w-4 h-4 text-blue-300 transition-all duration-200 {isDark
|
||||
? 'opacity-100 rotate-0 scale-100'
|
||||
: 'opacity-0 -rotate-90 scale-0'} absolute"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" />
|
||||
</svg>
|
||||
|
||||
<!-- invisible placeholder to maintain button size -->
|
||||
<div class="w-4 h-4 opacity-0">
|
||||
<svg class="w-4 h-4" viewBox="0 0 20 20">
|
||||
<path d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1z" />
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
@@ -1,6 +1,6 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5 text-slate-400"
|
||||
class="h-5 w-5 text-slate-400 dark:text-gray-400 transition-colors duration-200"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
|
||||
|
Before Width: | Height: | Size: 322 B After Width: | Height: | Size: 372 B |
@@ -30,7 +30,7 @@
|
||||
}
|
||||
}}
|
||||
transition:slide|global
|
||||
class="flex items-center bg-pleasant-gray text-gray-500 shadow-lg capitalize text-xl p-4 first:mt-0 mt-4 min-w-max rounded-md justify-self-center w-full"
|
||||
class="flex items-center bg-pleasant-gray dark:bg-gray-700 text-gray-500 dark:text-gray-200 shadow-lg dark:shadow-gray-900/50 capitalize text-xl p-4 first:mt-0 mt-4 min-w-max rounded-md justify-self-center w-full transition-colors duration-200"
|
||||
>
|
||||
<!-- <img class="w-9 mr-6" draggable="false" src={src1} alt="checkmark success" /> -->
|
||||
<svg class="w-11 mr-6" viewBox="0 0 32.25 32.4">
|
||||
@@ -60,7 +60,7 @@
|
||||
}
|
||||
}}
|
||||
transition:slide|global
|
||||
class="flex items-center bg-pleasant-gray text-gray-500 shadow-lg capitalize text-xl p-4 first:mt-0 mt-4 min-w-max rounded-md justify-self-center w-full"
|
||||
class="flex items-center bg-pleasant-gray dark:bg-gray-700 text-gray-500 dark:text-gray-200 shadow-lg dark:shadow-gray-900/50 capitalize text-xl p-4 first:mt-0 mt-4 min-w-max rounded-md justify-self-center w-full transition-colors duration-200"
|
||||
>
|
||||
<!-- <img class="w-9 mr-6" draggable="false" src="/t-warning.svg" alt="checkmark warning" /> -->
|
||||
<svg class="w-11 mr-6" viewBox="0 0 36.26 32.41">
|
||||
@@ -101,7 +101,7 @@
|
||||
}
|
||||
}}
|
||||
transition:slide|global
|
||||
class="flex items-center bg-pleasant-gray text-gray-500 shadow-lg capitalize text-xl p-4 first:mt-0 mt-4 min-w-max rounded-md justify-self-center w-full"
|
||||
class="flex items-center bg-pleasant-gray dark:bg-gray-700 text-gray-500 dark:text-gray-200 shadow-lg dark:shadow-gray-900/50 capitalize text-xl p-4 first:mt-0 mt-4 min-w-max rounded-md justify-self-center w-full transition-colors duration-200"
|
||||
>
|
||||
<!-- <img class="w-9 mr-6" draggable="false" src="/t-error.svg" alt="checkmark error" /> -->
|
||||
<svg class="w-11 mr-6" viewBox="0 0 32.98 32.98">
|
||||
@@ -143,7 +143,7 @@
|
||||
removeToast(toast.id);
|
||||
}
|
||||
}}
|
||||
class="flex items-center bg-pleasant-gray text-gray-500 shadow-lg capitalize text-xl p-4 first:mt-0 mt-4 min-w-max rounded-md justify-self-center w-full"
|
||||
class="flex items-center bg-pleasant-gray dark:bg-gray-700 text-gray-500 dark:text-gray-200 shadow-lg dark:shadow-gray-900/50 capitalize text-xl p-4 first:mt-0 mt-4 min-w-max rounded-md justify-self-center w-full transition-colors duration-200"
|
||||
>
|
||||
<!-- <img class="w-9 mr-6" draggable="false" src="/t-info.svg" alt="checkmark info" /> -->
|
||||
<svg class="w-11 mr-6" viewBox="0 0 32.79 32.79">
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="rounded-full bg-gray-600 text-white w-4 h-4 z-30 text-center ml-2 relative cursor-pointer hover:bg-gray-500"
|
||||
class="rounded-full bg-gray-600 dark:bg-gray-500 text-white w-4 h-4 z-30 text-center ml-2 relative cursor-pointer hover:bg-gray-500 dark:hover:bg-gray-400 transition-colors duration-200"
|
||||
role="tooltip"
|
||||
aria-describedby={tooltipId}
|
||||
on:mouseenter={(e) => {
|
||||
@@ -27,7 +27,7 @@
|
||||
{#if showToolTip}
|
||||
<div
|
||||
id={tooltipId}
|
||||
class="bg-gray-600 text-white w-max mt-2 px-2 py-2 rounded-md shadow-xl z-40"
|
||||
class="bg-gray-600 dark:bg-gray-700 text-white w-max mt-2 px-2 py-2 rounded-md shadow-xl dark:shadow-gray-900/70 z-40 transition-colors duration-200"
|
||||
style={tooltipStyle}
|
||||
>
|
||||
<p><slot /></p>
|
||||
|
||||
@@ -374,8 +374,10 @@
|
||||
let isDetailsVisible = $$slots.default;
|
||||
</script>
|
||||
|
||||
<div class="w-80vw z-[9000] col-start-1 col-end-4 flex flex-col">
|
||||
<div class="">
|
||||
<div
|
||||
class="w-80vw z-[9000] col-start-1 col-end-4 flex flex-col bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 transition-colors duration-200"
|
||||
>
|
||||
<div class="bg-white dark:bg-gray-800 transition-colors duration-200">
|
||||
<div class="mt-4 flex items-center flex-wrap">
|
||||
<!-- mode tabs -->
|
||||
<div class="flex">
|
||||
@@ -385,9 +387,10 @@
|
||||
isDetailsVisible = true;
|
||||
}}
|
||||
type="button"
|
||||
class="h-8 border-2 rounded-md w-36 text-center cursor-pointer hover:opacity-80 flex items-center justify-center gap-2 mb-2"
|
||||
class="h-8 border-2 border-gray-300 dark:border-gray-600 rounded-md w-36 text-center cursor-pointer hover:opacity-80 flex items-center justify-center gap-2 mb-2 text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 transition-colors duration-200"
|
||||
class:font-bold={isDetailsVisible}
|
||||
class:bg-cta-blue={isDetailsVisible}
|
||||
class:dark:bg-indigo-600={isDetailsVisible}
|
||||
class:text-white={isDetailsVisible}
|
||||
>
|
||||
<svg
|
||||
@@ -410,9 +413,10 @@
|
||||
isDetailsVisible = false;
|
||||
}}
|
||||
type="button"
|
||||
class="h-8 border-2 rounded-md w-36 text-center cursor-pointer hover:opacity-80 ml-1 flex items-center justify-center gap-2"
|
||||
class="h-8 border-2 border-gray-300 dark:border-gray-600 rounded-md w-36 text-center cursor-pointer hover:opacity-80 ml-1 flex items-center justify-center gap-2 text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 transition-colors duration-200"
|
||||
class:font-bold={!isDetailsVisible}
|
||||
class:bg-cta-blue={!isDetailsVisible}
|
||||
class:dark:bg-indigo-600={!isDetailsVisible}
|
||||
class:text-white={!isDetailsVisible}
|
||||
>
|
||||
<svg
|
||||
@@ -443,7 +447,7 @@
|
||||
<button
|
||||
type="button"
|
||||
on:click={triggerFileInput}
|
||||
class="h-8 border-2 rounded-md px-3 text-center cursor-pointer hover:opacity-80 flex items-center justify-center gap-2"
|
||||
class="h-8 border-2 border-gray-300 dark:border-gray-600 rounded-md px-3 text-center cursor-pointer hover:opacity-80 flex items-center justify-center gap-2 bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-200 transition-colors duration-200"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -476,9 +480,10 @@
|
||||
updatePreview();
|
||||
}
|
||||
}}
|
||||
class="h-8 border-2 rounded-md px-3 text-center cursor-pointer hover:opacity-80 flex items-center justify-center gap-2"
|
||||
class="h-8 border-2 border-gray-300 dark:border-gray-600 rounded-md px-3 text-center cursor-pointer hover:opacity-80 flex items-center justify-center gap-2 bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-200 transition-colors duration-200"
|
||||
class:font-bold={isPreviewVisible}
|
||||
class:bg-cta-blue={isPreviewVisible}
|
||||
class:dark:bg-indigo-600={isPreviewVisible}
|
||||
class:text-white={isPreviewVisible}
|
||||
>
|
||||
<svg
|
||||
@@ -499,7 +504,7 @@
|
||||
|
||||
<!-- template selector -->
|
||||
<select
|
||||
class="h-8 border-2 rounded-md px-3 bg-white text-black cursor-pointer"
|
||||
class="h-8 border-2 border-gray-300 dark:border-gray-600 rounded-md px-3 bg-white dark:bg-gray-700 text-black dark:text-gray-200 cursor-pointer transition-colors duration-200"
|
||||
on:change={(e) => {
|
||||
const t = /** @type {HTMLSelectElement} */ (e.target);
|
||||
if (t.value) {
|
||||
@@ -524,7 +529,7 @@
|
||||
id="domain-select"
|
||||
bind:value={selectedDomain}
|
||||
on:change={selectPreviewDomain}
|
||||
class="h-8 border-2 rounded-md px-3 bg-white text-black cursor-pointer"
|
||||
class="h-8 border-2 border-gray-300 dark:border-gray-600 rounded-md px-3 bg-white dark:bg-gray-700 text-black dark:text-gray-200 cursor-pointer transition-colors duration-200"
|
||||
>
|
||||
<option value="">Select preview domain...</option>
|
||||
{#each domainMap.values() as domain}
|
||||
@@ -537,7 +542,7 @@
|
||||
<button
|
||||
type="button"
|
||||
on:click={openFullPagePreview}
|
||||
class="h-8 border-2 rounded-md px-3 text-center cursor-pointer hover:opacity-80 flex items-center justify-center gap-2"
|
||||
class="h-8 border-2 border-gray-300 dark:border-gray-600 rounded-md px-3 text-center cursor-pointer hover:opacity-80 flex items-center justify-center gap-2 bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-200 transition-colors duration-200"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -563,8 +568,7 @@
|
||||
<!-- details -->
|
||||
{#if $$slots.default}
|
||||
<div
|
||||
class="flex flex-col lg:flex-row lg:items-center h-auto w-full justify-between mb-4"
|
||||
class:lg:h-28={isDetailsVisible}
|
||||
class="flex flex-col lg:flex-row lg:items-center h-auto w-full justify-between mb-4 bg-white dark:bg-gray-800 transition-colors duration-200"
|
||||
>
|
||||
{#if isDetailsVisible}
|
||||
<slot />
|
||||
@@ -575,21 +579,30 @@
|
||||
|
||||
<div class="flex h-full">
|
||||
<div
|
||||
class="flex flex-col border-2 border-black {!isPreviewVisible ? 'w-80vw' : 'w-1/2'}"
|
||||
class="flex flex-col border-2 border-black dark:border-gray-600 bg-white dark:bg-gray-900 {!isPreviewVisible
|
||||
? 'w-80vw'
|
||||
: 'w-1/2'} transition-colors duration-200"
|
||||
class:h-55vh={isDetailsVisible}
|
||||
class:h-67vh={!isDetailsVisible}
|
||||
>
|
||||
<div id="monaco-editor" class="h-full" />
|
||||
</div>
|
||||
<div class="bg-cta-blue cursor-move w-1" class:hidden={!isPreviewVisible}> </div>
|
||||
<div
|
||||
class="bg-cta-blue dark:bg-indigo-600 cursor-move w-1 transition-colors duration-200"
|
||||
class:hidden={!isPreviewVisible}
|
||||
>
|
||||
|
||||
</div>
|
||||
{#if isPreviewVisible}
|
||||
<div class="w-1/2 border-2 border-black">
|
||||
<div
|
||||
class="w-1/2 border-2 border-black dark:border-gray-600 bg-white transition-colors duration-200"
|
||||
>
|
||||
<iframe
|
||||
bind:this={previewFrame}
|
||||
sandbox="allow-forms allow-modals allow-popups allow-scripts allow-pointer-lock"
|
||||
title="preview"
|
||||
class="h-full w-full"
|
||||
style="color-scheme: normal"
|
||||
style="color-scheme: light;"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
let editor = null;
|
||||
let editorContainer = null;
|
||||
let isDark = false;
|
||||
|
||||
const heightClasses = {
|
||||
small: 'h-64',
|
||||
@@ -18,7 +19,38 @@
|
||||
large: 'h-96'
|
||||
};
|
||||
|
||||
// Check for dark mode
|
||||
const checkDarkMode = () => {
|
||||
if (typeof window !== 'undefined') {
|
||||
isDark = document.documentElement.classList.contains('dark');
|
||||
}
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
checkDarkMode();
|
||||
|
||||
// Watch for dark mode changes
|
||||
const observer = new MutationObserver(() => {
|
||||
const newIsDark = document.documentElement.classList.contains('dark');
|
||||
if (newIsDark !== isDark) {
|
||||
isDark = newIsDark;
|
||||
if (editor) {
|
||||
monaco.editor.setTheme(isDark ? 'vs-dark' : 'vs-light');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['class']
|
||||
});
|
||||
|
||||
const cleanup = () => {
|
||||
observer.disconnect();
|
||||
if (editor) {
|
||||
editor.dispose();
|
||||
}
|
||||
};
|
||||
self.MonacoEnvironment = {
|
||||
getWorker: function (_, label) {
|
||||
if (label === 'json') {
|
||||
@@ -31,7 +63,7 @@
|
||||
editor = monaco.editor.create(editorContainer, {
|
||||
value: value || '',
|
||||
language: language,
|
||||
theme: 'vs-dark',
|
||||
theme: isDark ? 'vs-dark' : 'vs-light',
|
||||
automaticLayout: true,
|
||||
minimap: {
|
||||
enabled: false
|
||||
@@ -60,11 +92,7 @@
|
||||
value = editor.getValue();
|
||||
});
|
||||
|
||||
return () => {
|
||||
if (editor) {
|
||||
editor.dispose();
|
||||
}
|
||||
};
|
||||
return cleanup;
|
||||
});
|
||||
|
||||
// Watch for external value changes
|
||||
@@ -86,30 +114,38 @@
|
||||
<div class="w-full">
|
||||
<div
|
||||
bind:this={editorContainer}
|
||||
class="border border-gray-300 rounded-md {heightClasses[height]} w-full"
|
||||
class="border border-gray-300 dark:border-gray-600 rounded-md {heightClasses[
|
||||
height
|
||||
]} w-full transition-colors duration-200"
|
||||
></div>
|
||||
{#if placeholder}
|
||||
<div class="mt-2">
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => (showExample = !showExample)}
|
||||
class="text-xs text-blue-600 hover:text-blue-800 underline focus:outline-none"
|
||||
class="text-xs text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 underline focus:outline-none transition-colors duration-200"
|
||||
>
|
||||
{showExample ? 'Hide' : 'Show'} example
|
||||
</button>
|
||||
{#if showExample}
|
||||
<div class="mt-2 p-3 bg-gray-50 border border-gray-200 rounded-md">
|
||||
<div
|
||||
class="mt-2 p-3 bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-md transition-colors duration-200"
|
||||
>
|
||||
<div class="flex justify-between items-start mb-2">
|
||||
<span class="text-xs font-medium text-gray-700">Example:</span>
|
||||
<span
|
||||
class="text-xs font-medium text-gray-700 dark:text-gray-300 transition-colors duration-200"
|
||||
>Example:</span
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
on:click={loadExample}
|
||||
class="text-xs text-blue-600 hover:text-blue-800 underline"
|
||||
class="text-xs text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 underline transition-colors duration-200"
|
||||
>
|
||||
Load example
|
||||
</button>
|
||||
</div>
|
||||
<pre class="text-xs text-gray-600 whitespace-pre-wrap">{placeholder}</pre>
|
||||
<pre
|
||||
class="text-xs text-gray-600 dark:text-gray-300 whitespace-pre-wrap transition-colors duration-200">{placeholder}</pre>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -92,19 +92,19 @@
|
||||
|
||||
<div class="flex">
|
||||
<nav
|
||||
class="hidden lg:flex flex-col transition-all fixed top-16 z-10 bg-gradient-to-b from-pc-darkblue to-indigo-400 rounded-br-lg overflow-y-auto overflow-x-hidden min-h-0 max-h-[calc(100vh-4rem)] box-content border-r-[1px] border-pc-darkblue"
|
||||
class="hidden lg:flex flex-col transition-all fixed top-16 z-10 bg-gradient-to-b from-pc-darkblue to-indigo-400 dark:from-gray-900 dark:to-gray-800 rounded-br-lg overflow-y-auto overflow-x-hidden min-h-0 max-h-[calc(100vh-4rem)] box-content border-r-[1px] border-pc-darkblue dark:border-gray-700"
|
||||
class:w-40={isExpanded}
|
||||
class:w-12={!isExpanded}
|
||||
>
|
||||
<div
|
||||
class="sticky top-0 bg-highlight-blue/20 border-b w-full border-blue-700/30 transform-none"
|
||||
class="sticky top-0 bg-highlight-blue/20 dark:bg-gray-800/70 border-b w-full border-blue-700/30 dark:border-gray-600 transform-none transition-colors duration-200"
|
||||
>
|
||||
<button
|
||||
class="w-full flex items-center justify-center rounded-md hover:bg-blue-600/30 transition-colors group px-3 py-2"
|
||||
class="w-full flex items-center justify-center rounded-md hover:bg-blue-600/30 dark:hover:bg-gray-700/70 transition-colors group px-3 py-2"
|
||||
on:click={() => (isExpanded = !isExpanded)}
|
||||
>
|
||||
<svg
|
||||
class="text-blue-100 duration-200 w-6"
|
||||
class="text-blue-100 dark:text-gray-100 duration-200 w-6 transition-colors"
|
||||
class:rotate-180={!isExpanded}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
@@ -123,13 +123,15 @@
|
||||
|
||||
<!-- Navigation Items -->
|
||||
<div
|
||||
class="flex flex-col py-4 flex-1 overflow-y-auto {scrollBarClassesVertical} [&::-webkit-scrollbar-track]:bg-cta-blue"
|
||||
class="flex flex-col py-4 flex-1 overflow-y-auto {scrollBarClassesVertical} [&::-webkit-scrollbar-track]:bg-cta-blue dark:[&::-webkit-scrollbar-track]:bg-gray-800"
|
||||
>
|
||||
{#each menu as link}
|
||||
{#if link.type === 'submenu'}
|
||||
<div class="py-1 mt-4 first:mt-0">
|
||||
{#if isExpanded}
|
||||
<div class="px-3 py-2 text-xs font-semibold text-blue-100 uppercase tracking-wider">
|
||||
<div
|
||||
class="px-3 py-2 text-xs font-semibold text-blue-100 dark:text-gray-200 uppercase tracking-wider transition-colors duration-200"
|
||||
>
|
||||
{link.label}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -139,8 +141,8 @@
|
||||
<a
|
||||
class="flex items-center px-3 py-2 text-sm transition-all duration-150 relative group
|
||||
{$page.url.pathname === item.route
|
||||
? 'text-white font-medium bg-active-blue shadow-md'
|
||||
: 'text-blue-100 hover:shadow-md hover:bg-highlight-blue hover:text-white'}"
|
||||
? 'text-white font-medium bg-active-blue dark:bg-indigo-600 shadow-md'
|
||||
: 'text-blue-100 dark:text-gray-200 hover:shadow-md hover:bg-highlight-blue dark:hover:bg-gray-700 hover:text-white dark:hover:text-gray-100'}"
|
||||
class:hidden={shouldHideMenuItem(item.route)}
|
||||
draggable="false"
|
||||
href={item.route}
|
||||
@@ -164,7 +166,7 @@
|
||||
{/if}
|
||||
|
||||
{#if $page.url.pathname === item.route}
|
||||
<div class="absolute left-0 top-0 bottom-0 w-1 bg-white"></div>
|
||||
<div class="absolute left-0 top-0 bottom-0 w-1 bg-white dark:bg-blue-400"></div>
|
||||
{/if}
|
||||
</a>
|
||||
{/each}
|
||||
@@ -174,8 +176,8 @@
|
||||
<a
|
||||
class="flex items-center px-3 py-2 text-sm transition-all duration-150 relative group
|
||||
{$page.url.pathname === link.route
|
||||
? 'text-white font-medium bg-active-blue shadow-md'
|
||||
: 'text-blue-100 hover:text-white'}"
|
||||
? 'text-white font-medium bg-active-blue dark:bg-indigo-600 shadow-md'
|
||||
: 'text-blue-100 dark:text-gray-200 hover:text-white dark:hover:text-gray-100 dark:hover:bg-gray-700'}"
|
||||
draggable="false"
|
||||
href={link.route}
|
||||
>
|
||||
@@ -188,16 +190,16 @@
|
||||
<span class="ml-3 truncate">{link.label}</span>
|
||||
{:else}
|
||||
<div
|
||||
class="absolute left-14 rounded bg-gray-900 text-white px-2 py-1 ml-6 text-sm
|
||||
invisible opacity-0 -translate-x-3 group-hover:visible group-hover:opacity-100 group-hover:translate-x-0
|
||||
transition-all duration-150 whitespace-nowrap z-50 shadow-lg"
|
||||
class="absolute left-14 rounded bg-gray-900 dark:bg-gray-800 text-white dark:text-gray-100 px-2 py-1 ml-6 text-sm
|
||||
invisible opacity-0 -translate-x-3 group-hover:visible group-hover:opacity-100 group-hover:translate-x-0
|
||||
transition-all duration-150 whitespace-nowrap z-50 shadow-lg border dark:border-gray-600"
|
||||
>
|
||||
{link.label}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $page.url.pathname === link.route}
|
||||
<div class="absolute left-0 top-0 bottom-0 w-1 bg-white"></div>
|
||||
<div class="absolute left-0 top-0 bottom-0 w-1 bg-white dark:bg-blue-400"></div>
|
||||
{/if}
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { AppStateService } from '$lib/service/appState';
|
||||
import { onMount } from 'svelte';
|
||||
import Logo from './Logo.svelte';
|
||||
import ThemeToggle from '../ThemeToggle.svelte';
|
||||
|
||||
const appState = AppStateService.instance;
|
||||
|
||||
@@ -112,17 +113,19 @@
|
||||
$: initials = getInitials(username || 'U');
|
||||
</script>
|
||||
|
||||
<div class="sticky top-0 z-20 col-span-12 h-16 bg-pc-darkblue flex justify-between items-center">
|
||||
<div
|
||||
class="header-container sticky top-0 z-20 col-span-12 h-16 bg-pc-darkblue dark:bg-gray-800 border-b border-pc-darkblue/20 dark:border-gray-700 flex justify-between items-center"
|
||||
>
|
||||
<Logo />
|
||||
{#if isInstalled}
|
||||
<div class="hidden lg:flex flex-row items-center px-8 h-full justify-self-end">
|
||||
{#if context.current === AppStateService.CONTEXT.COMPANY}
|
||||
<p class="text-slate-300 uppercase font-bold text-lg mr-4">
|
||||
<p class="text-slate-300 dark:text-gray-300 uppercase font-bold text-lg mr-4">
|
||||
{context.companyName}
|
||||
</p>
|
||||
{/if}
|
||||
<button
|
||||
class="rounded-md h-3/4 px-8 text-white bg-indigo-500 hover:bg-cta-blue uppercase font-semibold mr-4"
|
||||
class="rounded-md h-3/4 px-8 text-white bg-indigo-500 hover:bg-cta-blue dark:bg-indigo-600 dark:hover:bg-indigo-700 uppercase font-semibold mr-4 transition-colors duration-200"
|
||||
on:click={toggleChangeCompanyModal}
|
||||
>
|
||||
Change company
|
||||
@@ -130,14 +133,15 @@
|
||||
|
||||
{#if isUpdateAvailable}
|
||||
<a
|
||||
class="flex items-center gap-2 mr-8 text-lg font-medium text-white bg-gradient-to-r from-indigo-500 to-purple-500 rounded-md px-4 py-2 transition-all duration-300 transform hover:-translate-y-0.5 focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:ring-offset-2 active:scale-95 fixed bottom-4 right-2 shadow-md shadow-black"
|
||||
class="flex items-center gap-2 mr-8 text-lg font-medium text-white bg-gradient-to-r from-indigo-500 to-purple-500 dark:from-indigo-600 dark:to-purple-600 rounded-md px-4 py-2 transition-all duration-300 transform hover:-translate-y-0.5 focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:ring-offset-2 dark:focus:ring-offset-gray-800 active:scale-95 fixed bottom-4 right-2 shadow-md shadow-black dark:shadow-gray-900"
|
||||
href={'/settings/update'}
|
||||
>
|
||||
<span class="">✨</span>
|
||||
<span>Update Available</span>
|
||||
</a>
|
||||
{/if}
|
||||
<div class="relative ml-10 flex items-center">
|
||||
<div class="relative ml-10 flex items-center gap-4">
|
||||
<ThemeToggle />
|
||||
<button
|
||||
id="toggle-profile-menu"
|
||||
class="group flex items-center"
|
||||
@@ -145,7 +149,7 @@
|
||||
>
|
||||
<!-- Main Circle with Initials -->
|
||||
<div
|
||||
class="w-10 h-10 rounded-full bg-cta-blue hover:bg-indigo-500 flex items-center justify-center text-white font-medium relative"
|
||||
class="w-10 h-10 rounded-full bg-cta-blue hover:bg-indigo-500 dark:bg-indigo-600 dark:hover:bg-indigo-700 flex items-center justify-center text-white font-medium relative transition-colors duration-200"
|
||||
>
|
||||
{initials}
|
||||
|
||||
@@ -154,7 +158,7 @@
|
||||
|
||||
<!-- Dropdown Indicator -->
|
||||
<svg
|
||||
class="w-4 h-4 ml-2 text-gray-300 transition-transform duration-200 group-hover:text-white"
|
||||
class="w-4 h-4 ml-2 text-gray-300 dark:text-gray-400 transition-transform duration-200 group-hover:text-white"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -170,25 +174,40 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex lg:hidden items-center mr-4">
|
||||
<div class="flex lg:hidden items-center mr-4 gap-4">
|
||||
<div
|
||||
class="flex items-center justify-center w-10 h-10 rounded-lg hover:bg-white/10 transition-colors duration-200"
|
||||
>
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
<button
|
||||
class="rounded-md px-3 py-1 text-white bg-indigo-500 hover:bg-cta-blue uppercase font-semibold text-xs mr-3"
|
||||
class="rounded-md px-3 py-2 text-white bg-indigo-500 hover:bg-cta-blue dark:bg-indigo-600 dark:hover:bg-indigo-700 uppercase font-semibold text-xs transition-colors duration-200"
|
||||
on:click={toggleChangeCompanyModal}
|
||||
>
|
||||
Change company
|
||||
</button>
|
||||
<button class="flex w-14" on:click={() => (isMobileMenuVisible = !isMobileMenuVisible)}>
|
||||
<img class="" src="/mob-menu-button.svg" alt="toggle mobile menu" />
|
||||
<button
|
||||
class="flex items-center justify-center w-10 h-10 rounded-lg hover:bg-white/10 transition-colors duration-200"
|
||||
on:click={() => (isMobileMenuVisible = !isMobileMenuVisible)}
|
||||
>
|
||||
<img class="w-6 h-6" src="/mob-menu-button.svg" alt="toggle mobile menu" />
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
button {
|
||||
filter: contrast(1.1) saturate(1.2);
|
||||
/* Prevent any hover effects on the header */
|
||||
.header-container {
|
||||
background-color: #0b2063 !important;
|
||||
}
|
||||
button:hover {
|
||||
filter: contrast(1.2) saturate(1.3);
|
||||
.header-container:hover {
|
||||
background-color: #0b2063 !important;
|
||||
}
|
||||
:global(.dark) .header-container {
|
||||
background-color: #1f2937 !important;
|
||||
}
|
||||
:global(.dark) .header-container:hover {
|
||||
background-color: #1f2937 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,7 +7,22 @@
|
||||
on:keydown={(e) => e.key === 'Enter' && goto('/dashboard/')}
|
||||
tabindex="0"
|
||||
role="button"
|
||||
class="flex items-center w-40 sm:w-40 md:w-42 lg:w-56 justify-center py-4 my-6 rounded-md ml-4"
|
||||
class="flex items-center w-40 sm:w-40 md:w-42 lg:w-56 justify-center py-4 my-6 ml-4 cursor-pointer"
|
||||
>
|
||||
<img draggable="false" src="/logo-white.svg" alt="logo" />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
div {
|
||||
background: none !important;
|
||||
}
|
||||
div:hover {
|
||||
background: none !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
div:focus {
|
||||
background: none !important;
|
||||
background-color: transparent !important;
|
||||
outline: none !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,10 +5,13 @@
|
||||
</script>
|
||||
|
||||
<a
|
||||
class="py-2 px-4 text-white hover:bg-active-blue hover:text-white hover:rounded-md"
|
||||
class:bg-highlight-blue={$page.url.pathname === href}
|
||||
class:font-semibold={$page.url.pathname === href}
|
||||
class:rounded-md={$page.url.pathname === href}
|
||||
class="pl-5 py-2 text-white last:rounded-md first:rounded-t-md transition-colors duration-200"
|
||||
class:hover:shadow-md={$page.url.pathname !== href}
|
||||
class:hover:bg-highlight-blue={$page.url.pathname !== href}
|
||||
class:dark:hover:bg-gray-600={$page.url.pathname !== href}
|
||||
class:bg-active-blue={$page.url.pathname === href}
|
||||
class:dark:bg-gray-700={$page.url.pathname === href}
|
||||
class:shadow-md={$page.url.pathname === href}
|
||||
class:hidden
|
||||
{href}
|
||||
on:click
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { menu, mobileTopMenu } from '$lib/consts/navigation';
|
||||
import MenuLink from './MenuLink.svelte';
|
||||
import { shouldHideMenuItem } from '$lib/utils/common';
|
||||
import ThemeToggle from '../ThemeToggle.svelte';
|
||||
|
||||
export let visible = false;
|
||||
export let username = '';
|
||||
@@ -10,48 +11,91 @@
|
||||
</script>
|
||||
|
||||
{#if visible}
|
||||
<div class="fixed top-0 left-0 w-full h-full bg-pc-darkblue z-40 overflow-y-auto pb-4">
|
||||
<div class="flex justify-between h-16">
|
||||
<img class="w-40 sm:w-40 md:w-42 lg:w-56 ml-4" src="/logo-white.svg" alt="logo" />
|
||||
<button class="mr-4 w-14" on:click={() => (visible = !visible)}>
|
||||
<img class="w-3/4" src="/mob-menu-close.svg" alt="close mobile menu" />
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex flex-col px-4 py-4 rounded-b-xl">
|
||||
<div class="flex py-6 border-b-2 border-white mb-4">
|
||||
<!-- <div class="bg-slate-50 w-16 h-16 rounded-full" /> -->
|
||||
<div>
|
||||
<h1 class="font-bold text-3xl ml-6 text-white">{username ?? ''}</h1>
|
||||
<button
|
||||
on:click={onClickLogout}
|
||||
class="bg-cta-blue hover:bg-pc-lightblue uppercase font-bold ml-6 mt-2 py text-white rounded-md"
|
||||
>
|
||||
<p class="py px-8">Log Out</p>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col text-white">
|
||||
{#each mobileTopMenu as link}
|
||||
<a
|
||||
class="pl-5 py-2 hover:bg-cta-blue hover:text-white rounded-md"
|
||||
class:bg-gray-600={$page.url.pathname === link.route}
|
||||
class:hidden={shouldHideMenuItem(link.route)}
|
||||
on:click={() => (visible = !visible)}
|
||||
target={link.external ? '_blank' : '_self'}
|
||||
href={link.route}>{link.label}</a
|
||||
>
|
||||
{/each}
|
||||
<!-- Overlay -->
|
||||
<button
|
||||
class="fixed inset-0 bg-black bg-opacity-50 z-40 cursor-default"
|
||||
on:click={() => (visible = false)}
|
||||
aria-label="Close mobile menu"
|
||||
></button>
|
||||
|
||||
<!-- Mobile Menu -->
|
||||
<div
|
||||
class="mobile-menu-content fixed top-0 left-0 w-full h-full bg-pc-darkblue dark:bg-gray-900 z-50 overflow-y-auto shadow-xl transition-colors duration-200"
|
||||
>
|
||||
<!-- Header -->
|
||||
<div
|
||||
class="mobile-menu-header flex justify-between h-20 items-center bg-pc-darkblue dark:bg-gray-800 px-6"
|
||||
>
|
||||
<img class="w-40 h-auto" src="/logo-white.svg" alt="logo" />
|
||||
<div class="flex items-center gap-4">
|
||||
<div
|
||||
class="flex items-center justify-center w-12 h-12 rounded-lg hover:bg-white/10 dark:hover:bg-gray-600/30 transition-colors duration-200"
|
||||
>
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
<button
|
||||
class="flex items-center justify-center w-12 h-12 rounded-lg hover:bg-white/10 dark:hover:bg-gray-600/30 transition-colors duration-200"
|
||||
on:click={() => (visible = false)}
|
||||
>
|
||||
<img class="w-6 h-6" src="/mob-menu-close.svg" alt="close mobile menu" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex flex-col bg-cta-blue px-4 pt-4">
|
||||
|
||||
<!-- User Section -->
|
||||
<div class="p-6 border-b border-white dark:border-gray-700">
|
||||
<h1 class="font-bold text-xl text-white dark:text-gray-100 mb-4">
|
||||
{username ?? ''}
|
||||
</h1>
|
||||
<button
|
||||
on:click={onClickLogout}
|
||||
class="bg-cta-blue dark:bg-indigo-600 dark:hover:bg-indigo-700 uppercase font-bold py-3 px-6 rounded-md transition-colors duration-200 text-sm text-white"
|
||||
>
|
||||
Log Out
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Top Menu -->
|
||||
<div class="p-4">
|
||||
<div
|
||||
class="bg-gradient-to-b from-cta-blue to-indigo-500 dark:from-gray-800 dark:to-gray-700 rounded-md"
|
||||
>
|
||||
{#each mobileTopMenu as link}
|
||||
<a
|
||||
class="block text-center py-4 text-white text-lg font-medium first:rounded-t-md last:rounded-b-md transition-colors duration-200"
|
||||
class:bg-active-blue={$page.url.pathname === link.route}
|
||||
class:dark:bg-gray-700={$page.url.pathname === link.route}
|
||||
class:shadow-md={$page.url.pathname === link.route}
|
||||
class:hidden={shouldHideMenuItem(link.route)}
|
||||
on:click={() => (visible = false)}
|
||||
target={link.external ? '_blank' : '_self'}
|
||||
href={link.route}
|
||||
>
|
||||
{link.label}
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Menu -->
|
||||
<div class="p-4 pt-0">
|
||||
<div
|
||||
class="bg-gradient-to-b from-cta-blue to-indigo-500 dark:from-gray-800 dark:to-gray-700 rounded-md"
|
||||
>
|
||||
{#each menu as link}
|
||||
{#if link.type === 'submenu'}
|
||||
<div class="text-white font-semibold text-xl">{link.label}</div>
|
||||
<div
|
||||
class="text-center py-4 text-white font-semibold text-lg border-b border-white/20 dark:border-gray-600"
|
||||
>
|
||||
{link.label}
|
||||
</div>
|
||||
{#each link.items as item, i (i)}
|
||||
<MenuLink href={item.route} on:click={() => (visible = !visible)}>
|
||||
<a
|
||||
class="block text-center py-3 text-white text-base transition-colors duration-200"
|
||||
class:last:rounded-b-md={i === link.items.length - 1}
|
||||
href={item.route}
|
||||
on:click={() => (visible = false)}
|
||||
>
|
||||
{#if i === 0}
|
||||
Overview
|
||||
{:else if item.singleLabel}
|
||||
@@ -59,13 +103,49 @@
|
||||
{:else}
|
||||
{item.label}
|
||||
{/if}
|
||||
</MenuLink>
|
||||
</a>
|
||||
{/each}
|
||||
{:else}
|
||||
<MenuLink href={link.route}>{link.label}</MenuLink>
|
||||
<a
|
||||
class="block text-center py-4 text-white text-lg font-medium transition-colors duration-200"
|
||||
href={link.route}
|
||||
on:click={() => (visible = false)}
|
||||
>
|
||||
{link.label}
|
||||
</a>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
/* Prevent any hover effects on the mobile menu header */
|
||||
:global(.mobile-menu-header) {
|
||||
background-color: #0b2063 !important;
|
||||
}
|
||||
:global(.mobile-menu-header:hover) {
|
||||
background-color: #0b2063 !important;
|
||||
}
|
||||
:global(.dark .mobile-menu-header) {
|
||||
background-color: #1f2937 !important;
|
||||
}
|
||||
:global(.dark .mobile-menu-header:hover) {
|
||||
background-color: #1f2937 !important;
|
||||
}
|
||||
|
||||
/* Prevent any hover effects on the mobile menu content */
|
||||
.mobile-menu-content {
|
||||
background-color: #0b2063 !important;
|
||||
}
|
||||
.mobile-menu-content:hover {
|
||||
background-color: #0b2063 !important;
|
||||
}
|
||||
:global(.dark) .mobile-menu-content {
|
||||
background-color: #111827 !important;
|
||||
}
|
||||
:global(.dark) .mobile-menu-content:hover {
|
||||
background-color: #111827 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -26,6 +26,14 @@
|
||||
document.removeEventListener('click', handleClickOutsideNavigation);
|
||||
}
|
||||
}
|
||||
|
||||
// Custom transition that only fades in
|
||||
function fadeIn(node, { duration = 150 }) {
|
||||
return {
|
||||
duration,
|
||||
css: (t) => `opacity: ${t}`
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if visible}
|
||||
@@ -34,15 +42,17 @@
|
||||
class="lg:flex flex-col h-fit lg:col-start-10 lg:col-span-3 row-start-1 xl:col-start-11 xl:col-span-2 2xl:col-start-11 2xl:col-span-2 sticky top-20 z-30"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col bg-gradient-to-b from-cta-blue to-indigo-500 rounded-md"
|
||||
transition:fade={{ duration: 150 }}
|
||||
class="flex flex-col bg-gradient-to-b from-cta-blue to-indigo-500 dark:from-gray-800 dark:to-gray-700 rounded-md transition-colors duration-200"
|
||||
in:fadeIn={{ duration: 150 }}
|
||||
>
|
||||
{#each topMenu as item}
|
||||
<a
|
||||
class="pl-5 py-2 text-white last:rounded-md first:rounded-t-md"
|
||||
class="pl-5 py-2 text-white last:rounded-md first:rounded-t-md transition-colors duration-200"
|
||||
class:hover:shadow-md={$page.url.pathname !== item.route}
|
||||
class:hover:bg-highlight-blue={$page.url.pathname !== item.route}
|
||||
class:dark:hover:bg-gray-600={$page.url.pathname !== item.route}
|
||||
class:bg-active-blue={$page.url.pathname === item.route}
|
||||
class:dark:bg-gray-700={$page.url.pathname === item.route}
|
||||
class:shadow-md={$page.url.pathname === item.route}
|
||||
class:hidden={shouldHideMenuItem(item.route)}
|
||||
target={item.external ? '_blank' : '_self'}
|
||||
@@ -55,9 +65,11 @@
|
||||
{/each}
|
||||
<button
|
||||
on:click={logout}
|
||||
class="bg-white uppercase font-bold hover:bg-pc-lightblue py-2 mx-4 my-4 rounded-md"
|
||||
class="bg-white dark:bg-gray-800 uppercase font-bold hover:bg-pc-lightblue dark:hover:bg-gray-700 py-2 mx-4 my-4 rounded-md transition-colors duration-200"
|
||||
>
|
||||
<p class="text-cta-blue py px-8">Log Out</p>
|
||||
<p class="text-cta-blue dark:text-gray-100 py px-8 transition-colors duration-200">
|
||||
Log Out
|
||||
</p>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -117,17 +117,21 @@
|
||||
<div class="flex-grow p-6">
|
||||
{#if isLoadingCompanies}
|
||||
<div class="flex items-center justify-center py-8">
|
||||
<div class="text-gray-500">Loading companies...</div>
|
||||
<div class="text-gray-500 dark:text-gray-400 transition-colors duration-200">
|
||||
Loading companies...
|
||||
</div>
|
||||
</div>
|
||||
{:else if companies.length === 0}
|
||||
<div class="flex flex-col items-center justify-center py-8 text-center">
|
||||
<div class="text-gray-500 mb-4">No companies found.</div>
|
||||
<div class="text-sm text-gray-400 mb-4">
|
||||
<div class="text-gray-500 dark:text-gray-400 mb-4 transition-colors duration-200">
|
||||
No companies found.
|
||||
</div>
|
||||
<div class="text-sm text-gray-400 dark:text-gray-500 mb-4 transition-colors duration-200">
|
||||
You need to create a company first before you can switch to it.
|
||||
</div>
|
||||
<a
|
||||
href="/company/"
|
||||
class="bg-cta-blue hover:bg-blue-700 text-sm uppercase font-bold px-4 py-2 text-white rounded-md"
|
||||
class="bg-cta-blue dark:bg-blue-600 hover:bg-blue-700 dark:hover:bg-blue-700 text-sm uppercase font-bold px-4 py-2 text-white rounded-md transition-colors duration-200"
|
||||
on:click={() => {
|
||||
visible = false;
|
||||
}}
|
||||
@@ -143,11 +147,13 @@
|
||||
</div>
|
||||
|
||||
<!-- Button Section -->
|
||||
<div class="border-t p-6 mt-36 flex flex-wrap gap-4 justify-end">
|
||||
<div
|
||||
class="border-t border-gray-200 dark:border-gray-600 p-6 mt-36 flex flex-wrap gap-4 justify-end transition-colors duration-200"
|
||||
>
|
||||
{#if inContext}
|
||||
<button
|
||||
type="button"
|
||||
class="bg-slate-400 hover:bg-slate-300 text-sm mr-2 uppercase font-bold px-4 py-2 text-white rounded-md disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
class="bg-slate-400 dark:bg-gray-600 hover:bg-slate-300 dark:hover:bg-gray-500 text-sm mr-2 uppercase font-bold px-4 py-2 text-white rounded-md disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200"
|
||||
disabled={isLoadingCompanies}
|
||||
on:click={onClickSwitchToAdministratorContext}
|
||||
>
|
||||
@@ -157,7 +163,7 @@
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="bg-cta-blue hover:bg-blue-700 text-sm uppercase font-bold px-4 py-2 text-white rounded-md disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
class="bg-cta-blue dark:bg-blue-600 hover:bg-blue-700 dark:hover:bg-blue-700 text-sm uppercase font-bold px-4 py-2 text-white rounded-md disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200"
|
||||
disabled={isLoadingCompanies || !selectedCompany}
|
||||
on:click={onClickSwitch}
|
||||
>
|
||||
|
||||
@@ -46,20 +46,20 @@
|
||||
<!--
|
||||
<h3 class="text-lg font-medium text-gray-900">Delete {type}</h3>
|
||||
-->
|
||||
<p class="mt-2 text-gray-600">
|
||||
<p class="mt-2 text-gray-600 dark:text-gray-300">
|
||||
Are you sure you want to delete
|
||||
{#if name?.length > 30}
|
||||
<br />
|
||||
{/if}
|
||||
<span class="font-medium text-gray-900">"{name}"</span>?
|
||||
<span class="font-medium text-gray-900 dark:text-gray-100">"{name}"</span>?
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Impact Section -->
|
||||
{#if list.length}
|
||||
<div class="bg-gray-50 rounded-lg p-4">
|
||||
<p class="font-medium text-gray-900 mb-3">Side effects:</p>
|
||||
<ul class="space-y-2 ml-4 list-disc text-gray-600">
|
||||
<div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-4 transition-colors duration-200">
|
||||
<p class="font-medium text-gray-900 dark:text-gray-100 mb-3">Side effects:</p>
|
||||
<ul class="space-y-2 ml-4 list-disc text-gray-600 dark:text-gray-300">
|
||||
{#each list as line}
|
||||
<li>{line}</li>
|
||||
{/each}
|
||||
@@ -85,7 +85,7 @@
|
||||
{/if}
|
||||
|
||||
{#if permanent}
|
||||
<p class="text-red-700 font-medium">This action cannot be undone.</p>
|
||||
<p class="text-red-700 dark:text-red-400 font-medium">This action cannot be undone.</p>
|
||||
{/if}
|
||||
</div>
|
||||
</Alert>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</script>
|
||||
|
||||
<button
|
||||
class="hover:bg-gray-100 px-2 py-1 rounded-md transition-colors w-full text-left text-ellipsis overflow-hidden"
|
||||
class="hover:bg-gray-100 dark:hover:bg-gray-700 px-2 py-1 rounded-md transition-colors w-full text-left text-ellipsis overflow-hidden text-gray-900 dark:text-gray-100"
|
||||
title={text}
|
||||
on:click={() => onClickCopy(text)}
|
||||
>
|
||||
|
||||
@@ -5,12 +5,16 @@
|
||||
export let colspan = 100;
|
||||
</script>
|
||||
|
||||
<tr class="text-center bg-pleasant-gray">
|
||||
<tr class="text-center bg-pleasant-gray dark:bg-gray-700 transition-colors duration-200">
|
||||
<td class="p-24" {colspan}>
|
||||
{#if page === 1}
|
||||
<p class="text-lg text-gray-600">No {plural} found</p>
|
||||
<p class="text-lg text-gray-600 dark:text-gray-300 transition-colors duration-200">
|
||||
No {plural} found
|
||||
</p>
|
||||
{:else}
|
||||
<p class="text-lg text-gray-600">No more results found</p>
|
||||
<p class="text-lg text-gray-600 dark:text-gray-300 transition-colors duration-200">
|
||||
No more results found
|
||||
</p>
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex items-center text-ellipsis">
|
||||
<div
|
||||
class="flex items-center text-ellipsis text-gray-900 dark:text-gray-100 transition-colors duration-200"
|
||||
>
|
||||
<div class="w-4 h-4 {event.color} mr-2 rounded-sm"></div>
|
||||
{event.name}
|
||||
</div>
|
||||
|
||||
@@ -56,10 +56,13 @@
|
||||
<div
|
||||
bind:this={tableWrapper}
|
||||
class="
|
||||
border-2 rounded-md px-4 py-4 overflow-x-auto
|
||||
border-2 border-gray-200 dark:border-gray-600 rounded-md px-4 py-4 overflow-x-auto bg-white dark:bg-gray-800 transition-colors duration-200
|
||||
{scrollBarClassesHorizontal}"
|
||||
>
|
||||
<table class="w-full table-fixed" class:animate-pulse={isGhost}>
|
||||
<table
|
||||
class="w-full table-fixed bg-white dark:bg-gray-800 transition-colors duration-200"
|
||||
class:animate-pulse={isGhost}
|
||||
>
|
||||
<TableHeader {isGhost} {columns} {sortable} {hasActions} {pagination} />
|
||||
{#if !hasData && !isGhost}
|
||||
<EmptyTableResult page={currentPage} {plural} colspan={columnsLength} />
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
</script>
|
||||
|
||||
<td
|
||||
class={`pl-4 font-regular text-slate-600 text-ellipsis whitespace-nowrap overflow-hidden pr-4`}
|
||||
class={`pl-4 font-regular text-slate-600 dark:text-gray-300 text-ellipsis whitespace-nowrap overflow-hidden pr-4 transition-colors duration-200`}
|
||||
title={isDate ? '' : value}
|
||||
>
|
||||
{#if value}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<td class="pl-4 w-48 text-center border: hidden;">
|
||||
<p class="font-regular text-slate-600">
|
||||
<td class="w-48 text-center border: hidden;">
|
||||
<p class="font-regular text-slate-600 dark:text-gray-300 transition-colors duration-200">
|
||||
<slot />
|
||||
</p>
|
||||
</td>
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
</script>
|
||||
|
||||
<td class="pl-4 w-40 text-center border: hidden;">
|
||||
<p class="font-regular text-slate-600 flex justify-center">
|
||||
<p
|
||||
class="font-regular text-slate-600 dark:text-gray-300 flex justify-center transition-colors duration-200"
|
||||
>
|
||||
{value ? 'Yes' : 'No'}
|
||||
</p>
|
||||
</td>
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
<td class="pl-4 w-4 border: hidden;">
|
||||
|
||||
</td>
|
||||
<td class="pl-4 w-4 border: hidden; bg-white dark:bg-gray-800 transition-colors duration-200"> </td>
|
||||
|
||||
@@ -8,12 +8,16 @@
|
||||
</script>
|
||||
|
||||
{#if disabled}
|
||||
<button class="px py text-slate-300 cursor-not-allowed" {disabled} {title}>
|
||||
<button
|
||||
class="px py text-slate-300 dark:text-gray-500 cursor-not-allowed transition-colors duration-200"
|
||||
{disabled}
|
||||
{title}
|
||||
>
|
||||
<p class="ml-2 text-left">{name}</p>
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
class="px py-1 text-slate-600 hover:bg-red-400 hover:text-white cursor-pointer"
|
||||
class="px py-1 text-slate-600 dark:text-gray-300 hover:bg-red-400 dark:hover:bg-red-500 hover:text-white cursor-pointer transition-colors duration-200"
|
||||
on:click
|
||||
{title}
|
||||
>
|
||||
|
||||
@@ -16,12 +16,16 @@
|
||||
</script>
|
||||
|
||||
{#if disabled}
|
||||
<button class="px py-1 text-slate-300 cursor-not-allowed" {disabled} {title}>
|
||||
<button
|
||||
class="px py-1 text-slate-300 dark:text-gray-500 cursor-not-allowed transition-colors duration-200"
|
||||
{disabled}
|
||||
{title}
|
||||
>
|
||||
<p class="ml-2 text-left">{name}</p>
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
class="px py-1 text-slate-600 hover:bg-highlight-blue hover:text-white cursor-pointer"
|
||||
class="px py-1 text-slate-600 dark:text-gray-300 hover:bg-highlight-blue dark:hover:bg-blue-600 hover:text-white cursor-pointer transition-colors duration-200"
|
||||
on:click
|
||||
on:keydown={handleKeydown}
|
||||
{title}
|
||||
|
||||
@@ -128,16 +128,31 @@
|
||||
<div class="">
|
||||
<button
|
||||
bind:this={buttonRef}
|
||||
class="py-2 px-2"
|
||||
class="w-full h-full py-3 flex items-center justify-center"
|
||||
on:click|stopPropagation|preventDefault={toggle}
|
||||
on:keydown={handleKeydown}
|
||||
>
|
||||
<svg width="3.335557" height="16.465519" viewBox="0 0 0.88253281 4.3565019">
|
||||
<g transform="translate(-892.25669,88.863024)">
|
||||
<g transform="matrix(0,1.0139418,-1.0139418,0,802.48114,-807.2715)">
|
||||
<circle class="fill-cta-blue" cx="708.99603" cy="-88.976357" r="0.40846577" />
|
||||
<circle class="fill-cta-blue" cx="710.67859" cy="-88.976357" r="0.40846577" />
|
||||
<circle class="fill-cta-blue" cx="712.36115" cy="-88.976357" r="0.40846577" />
|
||||
<circle
|
||||
class="fill-cta-blue dark:fill-blue-500 transition-colors duration-200"
|
||||
cx="708.99603"
|
||||
cy="-88.976357"
|
||||
r="0.40846577"
|
||||
/>
|
||||
<circle
|
||||
class="fill-cta-blue dark:fill-blue-500 transition-colors duration-200"
|
||||
cx="710.67859"
|
||||
cy="-88.976357"
|
||||
r="0.40846577"
|
||||
/>
|
||||
<circle
|
||||
class="fill-cta-blue dark:fill-blue-500 transition-colors duration-200"
|
||||
cx="712.36115"
|
||||
cy="-88.976357"
|
||||
r="0.40846577"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
@@ -145,7 +160,7 @@
|
||||
|
||||
<div
|
||||
bind:this={menuRef}
|
||||
class="absolute bg-white drop-shadow-md z-20 w-48 rounded-md overflow-y-scroll {scrollBarClassesVertical}"
|
||||
class="absolute bg-white dark:bg-gray-800 drop-shadow-md dark:shadow-gray-900/50 border dark:border-gray-600 z-20 w-48 rounded-md overflow-y-scroll transition-colors duration-200 {scrollBarClassesVertical}"
|
||||
class:hidden={!isMenuVisible}
|
||||
>
|
||||
<ul class="flex flex-col text-left">
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<thead class="table-header-group">
|
||||
<slot />
|
||||
<thead class="table-header-group bg-white dark:bg-gray-800 transition-colors duration-200">
|
||||
<slot />
|
||||
</thead>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</script>
|
||||
|
||||
<th
|
||||
class="pl-4 bg-grayblue-light py-4 border-hidden first:rounded-tl-lg first:rounded-bl-lg last:rounded-tr-lg last:border-4 min-w-48"
|
||||
class="pl-4 bg-grayblue-light dark:bg-gray-700 py-4 border-hidden first:rounded-tl-lg first:rounded-bl-lg last:rounded-tr-lg last:border-4 min-w-48 transition-colors duration-200"
|
||||
class:rounded-br-lg={last}
|
||||
class:rounded-tr-lg={last}
|
||||
class:w-48={size === 'small'}
|
||||
@@ -49,7 +49,9 @@
|
||||
on:click|preventDefault={setSortAndSortBy}
|
||||
>
|
||||
<div class="w-full">
|
||||
<p class="font-bold text-slate-600 text-{alignText} flex">
|
||||
<p
|
||||
class="font-bold text-slate-600 dark:text-gray-200 text-{alignText} flex transition-colors duration-200"
|
||||
>
|
||||
{#if !isGhost}
|
||||
{title.length ? title : column}
|
||||
{:else}
|
||||
@@ -58,7 +60,7 @@
|
||||
{#if sortable && column.toLowerCase() === sortBy.toLowerCase() && !isGhost}
|
||||
<div
|
||||
class:bg-transparent={sortOrder === ''}
|
||||
class="flex justify-center items-center w-6 h-6 ml-2 rounded-md bg-cta-blue"
|
||||
class="flex justify-center items-center w-6 h-6 ml-2 rounded-md bg-cta-blue dark:bg-blue-600 transition-colors duration-200"
|
||||
>
|
||||
{#if sortOrder === 'asc'}
|
||||
<div>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<th class="bg-cta-light-blue w-20 py-4 border-hidden rounded-lg">
|
||||
<p class="font-bold text-slate-600 text-center">
|
||||
<th
|
||||
class="bg-cta-light-blue dark:bg-gray-700 w-20 py-4 border-hidden rounded-lg transition-colors duration-200"
|
||||
>
|
||||
<p class="font-bold text-slate-600 dark:text-gray-200 text-center transition-colors duration-200">
|
||||
<slot />
|
||||
</p>
|
||||
</th>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<th class="pl-4 bg-grayblue-light w-48 py-4 border-hidden">
|
||||
<p class="font-bold text-slate-600 flex justify-center text-center">
|
||||
<th
|
||||
class="pl-4 bg-grayblue-light dark:bg-gray-700 w-48 py-4 border-hidden transition-colors duration-200"
|
||||
>
|
||||
<p
|
||||
class="font-bold text-slate-600 dark:text-gray-200 flex justify-center text-center transition-colors duration-200"
|
||||
>
|
||||
<slot />
|
||||
</p>
|
||||
</th>
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
<th class="pl-4 bg-white w-4 py-4 border-hidden">
|
||||
|
||||
<th class="pl-4 bg-white dark:bg-gray-800 w-4 py-4 border-hidden transition-colors duration-200">
|
||||
</th>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<th class="pl-4 bg-grayblue-light w-48 py-4 border-hidden rounded-md">
|
||||
<p class="font-bold text-slate-600 text-left">
|
||||
<th
|
||||
class="pl-4 bg-grayblue-light dark:bg-gray-700 w-48 py-4 border-hidden rounded-md transition-colors duration-200"
|
||||
>
|
||||
<p class="font-bold text-slate-600 dark:text-gray-200 text-left transition-colors duration-200">
|
||||
<slot />
|
||||
</p>
|
||||
</th>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<tr class="table-row odd:bg-white even:bg-pleasant-gray h-10 hover:bg-cta-light-blue">
|
||||
<tr
|
||||
class="table-row odd:bg-white odd:dark:bg-gray-800 even:bg-pleasant-gray even:dark:bg-gray-900 h-10 hover:bg-cta-light-blue hover:dark:bg-indigo-800 transition-colors duration-200"
|
||||
>
|
||||
<slot />
|
||||
</tr>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<tr class="table-row bg-white h-4">
|
||||
<tr class="table-row bg-white dark:bg-gray-800 h-4 transition-colors duration-200">
|
||||
<slot />
|
||||
</tr>
|
||||
|
||||
+58
-54
@@ -1,104 +1,108 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
// modeLight is the state for light mode
|
||||
// mode constants
|
||||
export const modeLight = 'light';
|
||||
// modeDark is the state for dark mode
|
||||
export const modeDark = 'dark';
|
||||
// modeNotSet is the state for when the mode is not set
|
||||
const modeNotSet = 'not-set';
|
||||
// modeKey is the key used to store the mode in localstorage
|
||||
const modeKey = 'theme-mode';
|
||||
|
||||
/**
|
||||
* theme is either 'light' or 'dark' -
|
||||
* theme store - either 'light' or 'dark'
|
||||
* use it to know which theme is current and subscribe to know when it changes
|
||||
*/
|
||||
export const theme = writable('dark');
|
||||
export const theme = writable(modeLight);
|
||||
|
||||
/**
|
||||
* toggleMode is used to toggle the theme mode from light to dark or vice versa
|
||||
* it is used by the toggle button in the header
|
||||
* @returns {void} - the new mode, either light or dark
|
||||
* toggle the theme mode from light to dark or vice versa
|
||||
* @returns {void}
|
||||
*/
|
||||
export const toggleMode = () => {
|
||||
theme.update((current) => {
|
||||
let mode = current;
|
||||
switch (mode) {
|
||||
case modeLight:
|
||||
mode = modeDark;
|
||||
break;
|
||||
case modeDark:
|
||||
mode = modeLight;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown mode passed to changeMode: ${mode} - must be 'light' or 'dark'`);
|
||||
}
|
||||
setModeToLocalStorage(mode);
|
||||
return mode;
|
||||
const newMode = current === modeLight ? modeDark : modeLight;
|
||||
setModeToLocalStorage(newMode);
|
||||
applyThemeToHTML(newMode);
|
||||
return newMode;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* setupTheme is used to set the theme mode
|
||||
* it must be called once it the bootstrapping of the application
|
||||
* it checks if a prefered mode is set in localstorage, if not
|
||||
* it checks the OS preferred mode and if that is not set it defaults to light
|
||||
* the default mode is saved to localstorage
|
||||
*
|
||||
* @returns {string} - the mode that was set, either light or dark
|
||||
* setup the theme mode - must be called once during app bootstrapping
|
||||
* checks localStorage, then OS preference, defaults to light
|
||||
* @returns {string} the mode that was set
|
||||
*/
|
||||
export function setupTheme() {
|
||||
if (!browser) return modeLight;
|
||||
|
||||
let mode = getModeFromLocalStorage();
|
||||
if (mode === modeNotSet) {
|
||||
mode = getOSPreferredMode();
|
||||
setModeToLocalStorage(mode);
|
||||
}
|
||||
changeMode(mode);
|
||||
|
||||
applyThemeToHTML(mode);
|
||||
theme.set(mode);
|
||||
return mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* getOSPreferredMode is used to get the OS preferred light or dark mode
|
||||
* @returns {string} - the mode that was set, either light or dark
|
||||
* get the OS preferred light or dark mode
|
||||
* @returns {string} either 'light' or 'dark'
|
||||
*/
|
||||
export const getOSPreferredMode = () => {
|
||||
if (!browser) return modeLight;
|
||||
|
||||
const darkMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
if (darkMediaQuery.matches) {
|
||||
return modeDark;
|
||||
}
|
||||
return modeLight;
|
||||
return darkMediaQuery.matches ? modeDark : modeLight;
|
||||
};
|
||||
|
||||
/*
|
||||
* This function is used to set the theme mode in the local storage
|
||||
* @param {string} mode - light or dark
|
||||
* @returns {string}
|
||||
/**
|
||||
* get the theme mode from localStorage
|
||||
* @returns {string} the stored mode or 'not-set'
|
||||
*/
|
||||
export const getModeFromLocalStorage = () => {
|
||||
if (!browser) return modeNotSet;
|
||||
return localStorage.getItem(modeKey) ?? modeNotSet;
|
||||
};
|
||||
|
||||
/**
|
||||
* setModeToLocalStorage is used to set the theme mode in the local storage
|
||||
* @param {string} mode - light or dark
|
||||
* set the theme mode in localStorage
|
||||
* @param {string} mode - either 'light' or 'dark'
|
||||
*/
|
||||
export const setModeToLocalStorage = (mode) => {
|
||||
if (!browser) return;
|
||||
localStorage.setItem(modeKey, mode);
|
||||
};
|
||||
|
||||
/**
|
||||
* changeMode is used to change the theme mode from light to dark or vice versa
|
||||
* @param {string} mode - light or dark
|
||||
* apply the theme to the HTML element by adding/removing the 'dark' class
|
||||
* @param {string} mode - either 'light' or 'dark'
|
||||
*/
|
||||
const changeMode = (mode) => {
|
||||
switch (mode) {
|
||||
case modeLight:
|
||||
theme.set(modeLight);
|
||||
break;
|
||||
case modeDark:
|
||||
theme.set(modeDark);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown mode passed to changeMode: ${mode} - must be 'light' or 'dark'`);
|
||||
const applyThemeToHTML = (mode) => {
|
||||
if (!browser) return;
|
||||
|
||||
const html = document.documentElement;
|
||||
if (mode === modeDark) {
|
||||
html.classList.add('dark');
|
||||
} else {
|
||||
html.classList.remove('dark');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* set up theme change listener for OS preference changes
|
||||
*/
|
||||
export const setupOSThemeListener = () => {
|
||||
if (!browser) return;
|
||||
|
||||
const darkMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
darkMediaQuery.addEventListener('change', (e) => {
|
||||
// only update if user hasn't manually set a preference
|
||||
const storedMode = getModeFromLocalStorage();
|
||||
if (storedMode === modeNotSet) {
|
||||
const newMode = e.matches ? modeDark : modeLight;
|
||||
applyThemeToHTML(newMode);
|
||||
theme.set(newMode);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import DesktopMenu from '$lib/components/header/DesktopMenu.svelte';
|
||||
import { hideIsLoading, showIsLoading } from '$lib/store/loading';
|
||||
import Header from '$lib/components/header/Header.svelte';
|
||||
import { setupTheme, setupOSThemeListener } from '$lib/theme.js';
|
||||
// Removed feature flags import - no longer needed
|
||||
|
||||
// services
|
||||
@@ -50,6 +51,10 @@
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
// initialize theme system
|
||||
setupTheme();
|
||||
setupOSThemeListener();
|
||||
|
||||
(async () => {
|
||||
// handle session
|
||||
// if the user already has a active session, then the session will be
|
||||
@@ -197,7 +202,7 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<div class="flex flex-col min-w-[768px]">
|
||||
<!-- global components -->
|
||||
<DeveloperPanel />
|
||||
<Loader />
|
||||
@@ -235,6 +240,8 @@
|
||||
|
||||
/* font-family: "Phudu"; */
|
||||
font-family: 'Titillium Web';
|
||||
min-width: 768px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
:global(table) {
|
||||
user-select: text;
|
||||
|
||||
@@ -354,8 +354,10 @@
|
||||
<FormColumns>
|
||||
<FormColumn>
|
||||
<!-- Basic Information Section -->
|
||||
<div class="mb-6 pt-4 pb-2 border-b border-gray-200 w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue mb-3">Basic Information</h3>
|
||||
<div class="mb-6 pt-4 pb-2 border-b border-gray-200 dark:border-gray-600 w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue dark:text-white mb-3">
|
||||
Basic Information
|
||||
</h3>
|
||||
<TextField
|
||||
required
|
||||
minLength={1}
|
||||
@@ -366,8 +368,10 @@
|
||||
</div>
|
||||
|
||||
<!-- API Configuration Section -->
|
||||
<div class="mb-6 pt-4 pb-2 border-b border-gray-200 w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue mb-3">API Configuration</h3>
|
||||
<div class="mb-6 pt-4 pb-2 border-b border-gray-200 dark:border-gray-600 w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue dark:text-white mb-3">
|
||||
API Configuration
|
||||
</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="md:col-span-2">
|
||||
<TextField
|
||||
@@ -404,7 +408,9 @@
|
||||
|
||||
<!-- Headers and Body Section -->
|
||||
<div class="mb-6 pt-4 pb-2 w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue mb-3">Request Details</h3>
|
||||
<h3 class="text-base font-medium text-pc-darkblue dark:text-white mb-3">
|
||||
Request Details
|
||||
</h3>
|
||||
<div class="space-y-5">
|
||||
<TextareaField
|
||||
optional={true}
|
||||
@@ -420,9 +426,9 @@ X-Custom-Header: Hello Friend"
|
||||
>
|
||||
<div class="flex flex-col py-2 w-full">
|
||||
<div class="flex items-center">
|
||||
<p class="font-bold text-slate-600 py-2">Request Body</p>
|
||||
<div class="bg-gray-100 ml-2 px-2 rounded-md">
|
||||
<p class="text-slate-600 text-xs">optional</p>
|
||||
<p class="font-bold text-slate-600 dark:text-gray-300 py-2">Request Body</p>
|
||||
<div class="bg-gray-100 dark:bg-gray-700 ml-2 px-2 rounded-md">
|
||||
<p class="text-slate-600 dark:text-gray-300 text-xs">optional</p>
|
||||
</div>
|
||||
</div>
|
||||
<SimpleCodeEditor
|
||||
@@ -441,8 +447,10 @@ X-Custom-Header: Hello Friend"
|
||||
</div>
|
||||
|
||||
<!-- Response Validation Section -->
|
||||
<div class="mb-6 pt-4 pb-2 border-b border-gray-200 w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue mb-3">Response Validation</h3>
|
||||
<div class="mb-6 pt-4 pb-2 border-b border-gray-200 dark:border-gray-600 w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue dark:text-white mb-3">
|
||||
Response Validation
|
||||
</h3>
|
||||
<div class="space-y-5">
|
||||
<div>
|
||||
<TextField
|
||||
@@ -481,7 +489,9 @@ X-Custom-Header: Hello Friend"
|
||||
|
||||
<!-- Custom Fields Section -->
|
||||
<div class="pt-4 pb-2 w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue mb-3">Custom Fields</h3>
|
||||
<h3 class="text-base font-medium text-pc-darkblue dark:text-white mb-3">
|
||||
Custom Fields
|
||||
</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<TextField
|
||||
@@ -536,9 +546,13 @@ X-Custom-Header: Hello Friend"
|
||||
<div class="col-span-3 w-full overflow-y-auto px-6 py-4 space-y-6 select-text">
|
||||
{#if !testResponse.error}
|
||||
<!-- Successful Test -->
|
||||
<div class="mb-6 pt-4 pb-2 border-b border-gray-200 w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue mb-3">Request Details</h3>
|
||||
<div class="p-3 bg-gray-50 rounded-md border border-gray-200">
|
||||
<div class="mb-6 pt-4 pb-2 border-b border-gray-200 dark:border-gray-600 w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue dark:text-white mb-3">
|
||||
Request Details
|
||||
</h3>
|
||||
<div
|
||||
class="p-3 bg-gray-50 dark:bg-gray-800 rounded-md border border-gray-200 dark:border-gray-600"
|
||||
>
|
||||
<div class="font-medium">
|
||||
{testResponse.apiSender?.requestMethod}
|
||||
{testResponse?.request?.url}
|
||||
@@ -546,9 +560,13 @@ X-Custom-Header: Hello Friend"
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-6 pt-4 pb-2 border-b border-gray-200 w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue mb-3">Request Headers</h3>
|
||||
<div class="p-3 bg-gray-50 rounded-md border border-gray-200">
|
||||
<div class="mb-6 pt-4 pb-2 border-b border-gray-200 dark:border-gray-600 w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue dark:text-white mb-3">
|
||||
Request Headers
|
||||
</h3>
|
||||
<div
|
||||
class="p-3 bg-gray-50 dark:bg-gray-800 rounded-md border border-gray-200 dark:border-gray-600"
|
||||
>
|
||||
<pre class="text-xs whitespace-pre-wrap overflow-x-auto max-h-60">{headersToString(
|
||||
testResponse.request?.headers
|
||||
) || 'No headers'}</pre>
|
||||
@@ -556,17 +574,23 @@ X-Custom-Header: Hello Friend"
|
||||
</div>
|
||||
|
||||
<div class="pt-4 pb-2 w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue mb-3">Request Body</h3>
|
||||
<div class="p-3 bg-gray-50 rounded-md border border-gray-200">
|
||||
<h3 class="text-base font-medium text-pc-darkblue dark:text-white mb-3">Request Body</h3>
|
||||
<div
|
||||
class="p-3 bg-gray-50 dark:bg-gray-800 rounded-md border border-gray-200 dark:border-gray-600"
|
||||
>
|
||||
<pre class="text-xs whitespace-pre-wrap overflow-x-auto max-h-80">{testResponse.request
|
||||
?.body || 'Empty body'}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<!-- response -->
|
||||
<div class="mb-6 pt-4 pb-2 border-b border-gray-200 w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue mb-3">Response Status</h3>
|
||||
<div class="mb-6 pt-4 pb-2 border-b border-gray-200 dark:border-gray-600 w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue dark:text-white mb-3">
|
||||
Response Status
|
||||
</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="p-3 bg-gray-50 rounded-md border border-gray-200">
|
||||
<div
|
||||
class="p-3 bg-gray-50 dark:bg-gray-800 rounded-md border border-gray-200 dark:border-gray-600"
|
||||
>
|
||||
<div class="text-sm font-medium mb-1">Received:</div>
|
||||
<div
|
||||
class={testResponse.response?.code ==
|
||||
@@ -577,23 +601,31 @@ X-Custom-Header: Hello Friend"
|
||||
{testResponse.response?.code}
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-3 bg-gray-50 rounded-md border border-gray-200">
|
||||
<div
|
||||
class="p-3 bg-gray-50 dark:bg-gray-800 rounded-md border border-gray-200 dark:border-gray-600"
|
||||
>
|
||||
<div class="text-sm font-medium mb-1">Expected:</div>
|
||||
<div>{testResponse.apiSender?.expectedResponseStatusCode}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-6 pt-4 pb-2 border-b border-gray-200 w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue mb-3">Response Headers</h3>
|
||||
<div class="mb-6 pt-4 pb-2 border-b border-gray-200 dark:border-gray-600 w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue dark:text-white mb-3">
|
||||
Response Headers
|
||||
</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="p-3 bg-gray-50 rounded-md border border-gray-200">
|
||||
<div
|
||||
class="p-3 bg-gray-50 dark:bg-gray-800 rounded-md border border-gray-200 dark:border-gray-600"
|
||||
>
|
||||
<div class="text-sm font-medium mb-1">Received:</div>
|
||||
<pre class="text-xs whitespace-pre-wrap overflow-x-auto max-h-60">{headersToString(
|
||||
testResponse.response?.headers
|
||||
) || 'No headers'}</pre>
|
||||
</div>
|
||||
<div class="p-3 bg-gray-50 rounded-md border border-gray-200">
|
||||
<div
|
||||
class="p-3 bg-gray-50 dark:bg-gray-800 rounded-md border border-gray-200 dark:border-gray-600"
|
||||
>
|
||||
<div class="text-sm font-medium mb-1">Expected to contain:</div>
|
||||
<pre class="text-xs whitespace-pre-wrap overflow-x-auto max-h-60">{testResponse
|
||||
.apiSender?.expectedResponseHeaders || 'No validation specified'}</pre>
|
||||
@@ -602,14 +634,18 @@ X-Custom-Header: Hello Friend"
|
||||
</div>
|
||||
|
||||
<div class="pt-4 pb-2 w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue mb-3">Response Body</h3>
|
||||
<h3 class="text-base font-medium text-pc-darkblue dark:text-white mb-3">Response Body</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="p-3 bg-gray-50 rounded-md border border-gray-200">
|
||||
<div
|
||||
class="p-3 bg-gray-50 dark:bg-gray-800 rounded-md border border-gray-200 dark:border-gray-600"
|
||||
>
|
||||
<div class="text-sm font-medium mb-1">Received:</div>
|
||||
<pre class="text-xs whitespace-pre-wrap overflow-x-auto max-h-80">{testResponse
|
||||
.response?.body || 'Empty response'}</pre>
|
||||
</div>
|
||||
<div class="p-3 bg-gray-50 rounded-md border border-gray-200">
|
||||
<div
|
||||
class="p-3 bg-gray-50 dark:bg-gray-800 rounded-md border border-gray-200 dark:border-gray-600"
|
||||
>
|
||||
<div class="text-sm font-medium mb-1">Expected to contain:</div>
|
||||
<pre class="text-xs whitespace-pre-wrap overflow-x-auto max-h-80">{testResponse
|
||||
.apiSender?.expectedResponseBody || 'No validation specified'}</pre>
|
||||
@@ -618,9 +654,13 @@ X-Custom-Header: Hello Friend"
|
||||
</div>
|
||||
{:else}
|
||||
<!-- Error Case -->
|
||||
<div class="mb-6 pt-4 pb-2 border-b border-gray-200 w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue mb-3">Request Details</h3>
|
||||
<div class="p-3 bg-gray-50 rounded-md border border-gray-200">
|
||||
<div class="mb-6 pt-4 pb-2 border-b border-gray-200 dark:border-gray-600 w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue dark:text-white mb-3">
|
||||
Request Details
|
||||
</h3>
|
||||
<div
|
||||
class="p-3 bg-gray-50 dark:bg-gray-800 rounded-md border border-gray-200 dark:border-gray-600"
|
||||
>
|
||||
<div class="font-medium">
|
||||
{testResponse.apiSender?.requestMethod}
|
||||
{testResponse.apiSender?.requestURL}
|
||||
@@ -636,11 +676,11 @@ X-Custom-Header: Hello Friend"
|
||||
{/if}
|
||||
|
||||
<!-- Footer with Close Button -->
|
||||
<div class="pt-4 border-t border-gray-200">
|
||||
<div class="pt-4 border-t border-gray-200 dark:border-gray-600">
|
||||
<div class="flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
class="px-4 py-2 bg-white border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
class="px-4 py-2 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
on:click={closeTestModal}
|
||||
>
|
||||
Close
|
||||
|
||||
@@ -661,7 +661,9 @@ Simulation URLs to allow:\n${allowListingData.simulationUrl}\n
|
||||
<div class="col-span-3 w-full overflow-y-auto px-6 py-4 space-y-8">
|
||||
<!-- Basic Information Section -->
|
||||
<div class="w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue mb-3">Basic Information</h3>
|
||||
<h3 class="text-base font-medium text-pc-darkblue dark:text-white mb-3">
|
||||
Basic Information
|
||||
</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<TextField
|
||||
@@ -689,7 +691,9 @@ Simulation URLs to allow:\n${allowListingData.simulationUrl}\n
|
||||
|
||||
<!-- Delivery Configuration Section -->
|
||||
<div class="w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue mb-3">Delivery Configuration</h3>
|
||||
<h3 class="text-base font-medium text-pc-darkblue dark:text-white mb-3">
|
||||
Delivery Configuration
|
||||
</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
{#if formValues.templateType === 'Email' || !formValues.templateType}
|
||||
@@ -721,7 +725,9 @@ Simulation URLs to allow:\n${allowListingData.simulationUrl}\n
|
||||
|
||||
<!-- Domain & URL Configuration Section -->
|
||||
<div class="w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue mb-3">Domain & URL Configuration</h3>
|
||||
<h3 class="text-base font-medium text-pc-darkblue dark:text-white mb-3">
|
||||
Domain & URL Configuration
|
||||
</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-4">
|
||||
<div>
|
||||
<TextFieldSelect
|
||||
@@ -764,7 +770,7 @@ Simulation URLs to allow:\n${allowListingData.simulationUrl}\n
|
||||
|
||||
<!-- Page Flow Section -->
|
||||
<div class="w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue mb-3">Page Flow</h3>
|
||||
<h3 class="text-base font-medium text-pc-darkblue dark:text-white mb-3">Page Flow</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-5 gap-6">
|
||||
<div class="md:col-span-2 flex flex-col space-y-4">
|
||||
<div>
|
||||
|
||||
@@ -1042,7 +1042,9 @@
|
||||
<Modal headerText={modalText} visible={isModalVisible} onClose={closeModal} {isSubmitting}>
|
||||
<div class="relative flex justify-between items-center mb-8 w-full px-4">
|
||||
<!-- Connector Line -->
|
||||
<div class="absolute h-[2px] bg-gray-200 top-1/2 left-0 right-0 -translate-y-1/2 -z-10" />
|
||||
<div
|
||||
class="absolute h-[2px] bg-gray-200 dark:bg-gray-600 top-1/2 left-0 right-0 -translate-y-1/2 -z-10 transition-colors duration-200"
|
||||
/>
|
||||
|
||||
{#each campaignSteps as step, index}
|
||||
<div class="flex flex-col items-center w-20 sm:w-32">
|
||||
@@ -1053,10 +1055,10 @@
|
||||
transition-colors duration-200
|
||||
${
|
||||
currentStep > index + 1
|
||||
? 'bg-blue-300 text-white'
|
||||
? 'bg-blue-300 dark:bg-indigo-700 text-white'
|
||||
: currentStep === index + 1
|
||||
? 'bg-blue-600 text-white'
|
||||
: 'bg-white text-gray-500 border-2 border-gray-300'
|
||||
? 'bg-blue-600 dark:bg-indigo-600 text-white'
|
||||
: 'bg-white dark:bg-gray-700 text-gray-500 dark:text-gray-300 border-2 border-gray-300 dark:border-gray-600'
|
||||
}
|
||||
`}
|
||||
>
|
||||
@@ -1083,7 +1085,9 @@
|
||||
class={`
|
||||
mt-2 text-xs sm:text-sm font-medium text-center
|
||||
${
|
||||
currentStep > index + 1 || currentStep === index + 1 ? 'text-blue-600' : 'text-gray-500'
|
||||
currentStep > index + 1 || currentStep === index + 1
|
||||
? 'text-blue-600 dark:text-blue-400'
|
||||
: 'text-gray-500 dark:text-gray-400'
|
||||
}
|
||||
`}
|
||||
>
|
||||
@@ -1167,7 +1171,7 @@
|
||||
Delivery start
|
||||
</DateTimeField>
|
||||
<button
|
||||
class="text-cta-blue hover:text-blue-700 text-sm"
|
||||
class="text-cta-blue hover:text-blue-700 dark:text-white dark:hover:text-gray-200 text-sm transition-colors duration-200"
|
||||
on:click|preventDefault={() =>
|
||||
(formValues.sendStartAt = new Date().toISOString())}
|
||||
>
|
||||
@@ -1178,7 +1182,9 @@
|
||||
{#if formValues.sendStartAt}
|
||||
<div class="pl-36 pt-4 pb-6">
|
||||
<div class="flex flex-col gap-2">
|
||||
<p class="text-sm font-semibold text-slate-600">
|
||||
<p
|
||||
class="text-sm font-semibold text-slate-600 dark:text-gray-300 transition-colors duration-200"
|
||||
>
|
||||
Distribution Speed
|
||||
|
||||
<span class="italic font-normal">
|
||||
@@ -1237,7 +1243,7 @@
|
||||
</DateTimeField>
|
||||
{#if spreadOption === SPREAD_MANUAL}
|
||||
<button
|
||||
class="text-cta-blue hover:text-blue-700 text-sm"
|
||||
class="text-cta-blue hover:text-blue-700 dark:text-white dark:hover:text-gray-200 text-sm transition-colors duration-200"
|
||||
on:click|preventDefault={() => {
|
||||
formValues.sendEndAt = new Date().toISOString();
|
||||
}}
|
||||
@@ -1472,38 +1478,56 @@
|
||||
<!-- First Row: Basic Info and Recipients -->
|
||||
<div class="grid grid-cols-2 gap-6">
|
||||
<!-- Basic Information -->
|
||||
<div class="bg-white p-6 rounded-lg shadow-sm">
|
||||
<h3 class="text-xl font-semibold text-pc-darkblue mb-4 border-b pb-2">
|
||||
<div
|
||||
class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-sm transition-colors duration-200"
|
||||
>
|
||||
<h3
|
||||
class="text-xl font-semibold text-pc-darkblue dark:text-white mb-4 border-b dark:border-gray-600 pb-2 transition-colors duration-200"
|
||||
>
|
||||
Basic Information
|
||||
</h3>
|
||||
<div class="grid grid-cols-[120px_1fr] gap-y-3">
|
||||
<span class="text-grayblue-dark font-medium">Name:</span>
|
||||
<span class="text-pc-darkblue">{formValues.name || 'Not set'}</span>
|
||||
<span class="text-pc-darkblue dark:text-white"
|
||||
>{formValues.name || 'Not set'}</span
|
||||
>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Template:</span>
|
||||
<span class="text-pc-darkblue">{formValues.template || 'Not set'}</span>
|
||||
<span class="text-pc-darkblue dark:text-white"
|
||||
>{formValues.template || 'Not set'}</span
|
||||
>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Type:</span>
|
||||
<span class="text-pc-darkblue">{formValues.isTest ? 'Test' : 'Production'}</span
|
||||
<span class="text-pc-darkblue dark:text-white"
|
||||
>{formValues.isTest ? 'Test' : 'Production'}</span
|
||||
>
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recipients -->
|
||||
<div class="bg-white p-6 rounded-lg shadow-sm">
|
||||
<h3 class="text-xl font-semibold text-pc-darkblue mb-4 border-b pb-2">
|
||||
<div
|
||||
class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-sm transition-colors duration-200"
|
||||
>
|
||||
<h3
|
||||
class="text-xl font-semibold text-pc-darkblue dark:text-white mb-4 border-b dark:border-gray-600 pb-2 transition-colors duration-200"
|
||||
>
|
||||
Recipients
|
||||
</h3>
|
||||
<div class="grid grid-cols-[120px_1fr] gap-y-3">
|
||||
<span class="text-grayblue-dark font-medium">Groups:</span>
|
||||
<span class="text-pc-darkblue">
|
||||
<span
|
||||
class="text-pc-darkblue dark:text-gray-100 transition-colors duration-200"
|
||||
>
|
||||
{formValues.recipientGroups.length
|
||||
? formValues.recipientGroups.join(', ')
|
||||
: 'None selected'}
|
||||
</span>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Total:</span>
|
||||
<span class="text-pc-darkblue">{formValues.selectedCount} recipients</span>
|
||||
<span class="text-pc-darkblue dark:text-white"
|
||||
>{formValues.selectedCount} recipients</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1511,8 +1535,12 @@
|
||||
<!-- Second Row: Email and Schedule -->
|
||||
<div class="grid grid-cols-2 gap-6">
|
||||
<!-- Email/API Sender Information -->
|
||||
<div class="bg-white p-6 rounded-lg shadow-sm">
|
||||
<h3 class="text-xl font-semibold text-pc-darkblue mb-4 border-b pb-2">
|
||||
<div
|
||||
class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-sm transition-colors duration-200"
|
||||
>
|
||||
<h3
|
||||
class="text-xl font-semibold text-pc-darkblue dark:text-white mb-4 border-b dark:border-gray-600 pb-2 transition-colors duration-200"
|
||||
>
|
||||
Delivery Details
|
||||
</h3>
|
||||
{#await getTemplateDetails(formValues.template)}
|
||||
@@ -1521,36 +1549,44 @@
|
||||
{#if template?.apiSender}
|
||||
<div class="grid grid-cols-[120px_1fr] gap-y-3">
|
||||
<span class="text-grayblue-dark font-medium">Type:</span>
|
||||
<span class="text-pc-darkblue font-semibold">API Sender</span>
|
||||
<span class="text-pc-darkblue dark:text-white font-semibold"
|
||||
>API Sender</span
|
||||
>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Name:</span>
|
||||
<span class="text-pc-darkblue">{template.apiSender.name || 'Not set'}</span>
|
||||
<span class="text-pc-darkblue dark:text-white"
|
||||
>{template.apiSender.name || 'Not set'}</span
|
||||
>
|
||||
|
||||
{#if template?.email}
|
||||
<span class="text-grayblue-dark font-medium">Email:</span>
|
||||
<span class="text-pc-darkblue">{template.email.name || 'Not set'}</span>
|
||||
<span class="text-pc-darkblue dark:text-white"
|
||||
>{template.email.name || 'Not set'}</span
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if template?.email}
|
||||
<div class="grid grid-cols-[120px_1fr] gap-y-3">
|
||||
<span class="text-grayblue-dark font-medium">Type:</span>
|
||||
<span class="text-pc-darkblue font-semibold">SMTP</span>
|
||||
<span class="text-pc-darkblue dark:text-white font-semibold">SMTP</span>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Name:</span>
|
||||
<span class="text-pc-darkblue">{template.email.name || 'Not set'}</span>
|
||||
<span class="text-pc-darkblue dark:text-white"
|
||||
>{template.email.name || 'Not set'}</span
|
||||
>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">From:</span>
|
||||
<span class="text-pc-darkblue"
|
||||
<span class="text-pc-darkblue dark:text-white"
|
||||
>{template.email.mailHeaderFrom || 'Not set'}</span
|
||||
>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Mail from:</span>
|
||||
<span class="text-pc-darkblue"
|
||||
<span class="text-pc-darkblue dark:text-white"
|
||||
>{template.email.mailEnvelopeFrom || 'Not set'}</span
|
||||
>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Subject:</span>
|
||||
<span class="text-pc-darkblue"
|
||||
<span class="text-pc-darkblue dark:text-white"
|
||||
>{template.email.mailHeaderSubject || 'Not set'}</span
|
||||
>
|
||||
</div>
|
||||
@@ -1565,43 +1601,58 @@
|
||||
</div>
|
||||
|
||||
<!-- Schedule -->
|
||||
<div class="bg-white p-6 rounded-lg shadow-sm">
|
||||
<h3 class="text-xl font-semibold text-pc-darkblue mb-4 border-b pb-2">
|
||||
<div
|
||||
class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-sm transition-colors duration-200"
|
||||
>
|
||||
<h3
|
||||
class="text-xl font-semibold text-pc-darkblue dark:text-white mb-4 border-b dark:border-gray-600 pb-2 transition-colors duration-200"
|
||||
>
|
||||
Schedule
|
||||
</h3>
|
||||
<div class="grid grid-cols-[120px_1fr] gap-y-3">
|
||||
<span class="text-grayblue-dark font-medium">Type:</span>
|
||||
<span class="text-pc-darkblue capitalize">{scheduleType}</span>
|
||||
<span class="text-pc-darkblue dark:text-white capitalize">{scheduleType}</span>
|
||||
|
||||
{#if scheduleType === 'basic'}
|
||||
<span class="text-grayblue-dark font-medium">Start:</span>
|
||||
<span class="text-pc-darkblue">
|
||||
<span
|
||||
class="text-pc-darkblue dark:text-gray-100 transition-colors duration-200"
|
||||
>
|
||||
<Datetime value={formValues.sendStartAt} />
|
||||
<RelativeTime value={formValues.sendStartAt} />
|
||||
</span>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">End:</span>
|
||||
<span class="text-pc-darkblue">
|
||||
<span
|
||||
class="text-grayblue-dark dark:text-gray-300 font-medium transition-colors duration-200"
|
||||
>End:</span
|
||||
>
|
||||
<span
|
||||
class="text-pc-darkblue dark:text-gray-100 transition-colors duration-200"
|
||||
>
|
||||
<Datetime value={formValues.sendEndAt} />
|
||||
<RelativeTime value={formValues.sendEndAt} />
|
||||
</span>
|
||||
|
||||
{#if spreadOption && spreadOption !== SPREAD_MANUAL}
|
||||
<span class="text-grayblue-dark font-medium">Spread:</span>
|
||||
<span class="text-pc-darkblue">
|
||||
<span
|
||||
class="text-pc-darkblue dark:text-gray-100 transition-colors duration-200"
|
||||
>
|
||||
{spreadOptionMap.byValue(spreadOption)}
|
||||
</span>
|
||||
{/if}
|
||||
{:else if scheduleType === 'schedule'}
|
||||
<span class="text-grayblue-dark font-medium">Active days:</span>
|
||||
<span class="text-pc-darkblue">
|
||||
<span
|
||||
class="text-pc-darkblue dark:text-gray-100 transition-colors duration-200"
|
||||
>
|
||||
{formValues.constraintWeekDays.map((d) => dayMap[d]).join(', ') ||
|
||||
'None selected'}
|
||||
</span>
|
||||
|
||||
{#if formValues.contraintStartTime && formValues.contraintEndTime}
|
||||
<span class="text-grayblue-dark font-medium">Hours:</span>
|
||||
<span class="text-pc-darkblue">
|
||||
<span class="text-pc-darkblue dark:text-white">
|
||||
{formValues.contraintStartTime} - {formValues.contraintEndTime}
|
||||
</span>
|
||||
{/if}
|
||||
@@ -1609,7 +1660,7 @@
|
||||
|
||||
{#if formValues.closeAt}
|
||||
<span class="text-grayblue-dark font-medium">Close at:</span>
|
||||
<span class="text-pc-darkblue">
|
||||
<span class="text-pc-darkblue dark:text-white">
|
||||
<Datetime value={formValues.closeAt} />
|
||||
<RelativeTime value={formValues.closeAt} />
|
||||
</span>
|
||||
@@ -1620,13 +1671,17 @@
|
||||
|
||||
<!-- Third Row: Security & Privacy -->
|
||||
<div class="grid grid-cols-1 gap-6">
|
||||
<div class="bg-white p-6 rounded-lg shadow-sm">
|
||||
<h3 class="text-xl font-semibold text-pc-darkblue mb-4 border-b pb-2">
|
||||
<div
|
||||
class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-sm transition-colors duration-200"
|
||||
>
|
||||
<h3
|
||||
class="text-xl font-semibold text-pc-darkblue dark:text-white mb-4 border-b dark:border-gray-600 pb-2 transition-colors duration-200"
|
||||
>
|
||||
Security & Privacy
|
||||
</h3>
|
||||
<div class="grid grid-cols-[120px_1fr] gap-y-3">
|
||||
<span class="text-grayblue-dark font-medium">IP Filtering:</span>
|
||||
<span class="text-pc-darkblue">
|
||||
<span class="text-pc-darkblue dark:text-white">
|
||||
{#if allowDenyType === 'none'}
|
||||
None
|
||||
{:else}
|
||||
@@ -1638,7 +1693,7 @@
|
||||
</span>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Save data:</span>
|
||||
<span class="text-pc-darkblue"
|
||||
<span class="text-pc-darkblue dark:text-white"
|
||||
>{formValues.saveSubmittedData ? 'Enabled' : 'Disabled'}</span
|
||||
>
|
||||
|
||||
@@ -1651,17 +1706,20 @@
|
||||
|
||||
{#if formValues.webhookValue}
|
||||
<span class="text-grayblue-dark font-medium">Webhook:</span>
|
||||
<span class="text-pc-darkblue">{formValues.webhookValue}</span>
|
||||
<span class="text-pc-darkblue dark:text-white">{formValues.webhookValue}</span
|
||||
>
|
||||
{/if}
|
||||
|
||||
{#if formValues.denyPageValue}
|
||||
<span class="text-grayblue-dark font-medium">Deny Page:</span>
|
||||
<span class="text-pc-darkblue">{formValues.denyPageValue}</span>
|
||||
<span class="text-pc-darkblue dark:text-white"
|
||||
>{formValues.denyPageValue}</span
|
||||
>
|
||||
{/if}
|
||||
|
||||
{#if formValues.anonymizeAt}
|
||||
<span class="text-grayblue-dark font-medium">Anonymize at:</span>
|
||||
<span class="text-pc-darkblue">
|
||||
<span class="text-pc-darkblue dark:text-white">
|
||||
<Datetime value={formValues.anonymizeAt} />
|
||||
<RelativeTime value={formValues.anonymizeAt} />
|
||||
</span>
|
||||
@@ -1674,11 +1732,13 @@
|
||||
</FormColumns>
|
||||
{/if}
|
||||
<FormError message={modalError} />
|
||||
<div class="col-span-3 flex justify-between items-center w-full mt-2 border-t py-6">
|
||||
<div
|
||||
class="col-span-3 flex justify-between items-center w-full mt-2 border-t dark:border-gray-600 py-6 transition-colors duration-200"
|
||||
>
|
||||
{#if currentStep > 1}
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
class="inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
on:click={previousStep}
|
||||
>
|
||||
<svg
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
import EventName from '$lib/components/table/EventName.svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { globalButtonDisabledAttributes } from '$lib/utils/form';
|
||||
import FileField from '$lib/components/FileField.svelte';
|
||||
|
||||
// services
|
||||
const appStateService = AppStateService.instance;
|
||||
@@ -1062,18 +1063,20 @@
|
||||
<!-- First Row: Details and Timeline -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2">
|
||||
<!-- Primary Campaign Info -->
|
||||
<div class="bg-white py-6 rounded-lg">
|
||||
<h3 class="text-xl font-semibold text-pc-darkblue mb-4 border-b pb-2">Details</h3>
|
||||
<div class="py-6 rounded-lg">
|
||||
<h3 class="text-xl font-semibold text-pc-darkblue dark:text-white mb-4 border-b pb-2">
|
||||
Details
|
||||
</h3>
|
||||
<div class="grid grid-cols-[120px_1fr] gap-y-3">
|
||||
<span class="text-grayblue-dark font-medium">Status:</span>
|
||||
<span class="text-pc-darkblue font-semibold">
|
||||
<span class="text-pc-darkblue dark:text-white font-semibold">
|
||||
{toEvent(campaign.notableEventName).name}
|
||||
</span>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Template:</span>
|
||||
<span class="text-pc-darkblue">
|
||||
<span class="text-pc-darkblue dark:text-white">
|
||||
<button
|
||||
class="cursor-pointer text-cta-blue underline hover:opacity-75"
|
||||
class="cursor-pointer text-cta-blue dark:text-white underline hover:opacity-75"
|
||||
on:click={() => {
|
||||
openTemplateModal(templateMap.byValue(campaign.template));
|
||||
}}
|
||||
@@ -1083,10 +1086,10 @@
|
||||
</span>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Groups:</span>
|
||||
<span class="text-pc-darkblue">
|
||||
<span class="text-pc-darkblue dark:text-white">
|
||||
{#each campaign.recipientGroups as group, i}
|
||||
<a
|
||||
class="hover:underline text-cta-blue hover:opacity-75 underline"
|
||||
class="hover:underline text-cta-blue dark:text-white hover:opacity-75 underline"
|
||||
href="/recipient/group/{recipientGroupMap.byValue(group)}"
|
||||
target="_blank">{group}</a
|
||||
>{#if i !== (campaign.recipientGroups?.length ?? 0) - 1}
|
||||
@@ -1096,18 +1099,20 @@
|
||||
</span>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Type:</span>
|
||||
<span class="text-pc-darkblue">{isSelfManaged ? 'Self Managed' : 'Scheduled'}</span>
|
||||
<span class="text-pc-darkblue dark:text-white"
|
||||
>{isSelfManaged ? 'Self Managed' : 'Scheduled'}</span
|
||||
>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">
|
||||
{campaign?.allowDeny ? (campaign.allowDeny.allowed ? 'Allow' : 'Deny') : ''}
|
||||
IP Filters:
|
||||
</span>
|
||||
<span class="text-pc-darkblue">
|
||||
<span class="text-pc-darkblue dark:text-white">
|
||||
{#if campaign.allowDeny?.length}
|
||||
{#each campaign.allowDeny as allowDeny, i}
|
||||
<a
|
||||
href="/ip-filter/?edit={allowDeny.id}"
|
||||
class="text-pc-blue hover:underline"
|
||||
class="text-cta-blue dark:text-white hover:underline"
|
||||
target="_blank"
|
||||
>
|
||||
{allowDeny.name}
|
||||
@@ -1121,7 +1126,7 @@
|
||||
</span>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Webhook:</span>
|
||||
<span class="text-pc-darkblue">
|
||||
<span class="text-pc-darkblue dark:text-white">
|
||||
{#if campaign.webhookID}
|
||||
Yes
|
||||
{:else}
|
||||
@@ -1130,44 +1135,60 @@
|
||||
</span>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Data Saving:</span>
|
||||
<span class="text-pc-darkblue">
|
||||
<span class="text-pc-darkblue dark:text-white">
|
||||
{campaign.saveSubmittedData ? 'Enabled' : 'Disabled'}
|
||||
</span>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Test:</span>
|
||||
<span class="text-pc-darkblue">{campaign.isTest ? 'Yes' : 'No'}</span>
|
||||
<span class="text-pc-darkblue dark:text-white">{campaign.isTest ? 'Yes' : 'No'}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-6 rounded-lg">
|
||||
<h3 class="text-xl font-semibold text-pc-darkblue mb-4 border-b pb-2">Timeline</h3>
|
||||
<div class="p-6 rounded-lg">
|
||||
<h3 class="text-xl font-semibold text-pc-darkblue dark:text-white mb-4 border-b pb-2">
|
||||
Timeline
|
||||
</h3>
|
||||
<div class="grid grid-cols-[120px_1fr] gap-y-3">
|
||||
<span class="text-grayblue-dark font-medium">Created:</span>
|
||||
<span class="text-pc-darkblue"><Datetime value={campaign.createdAt} /></span>
|
||||
<span class="text-pc-darkblue dark:text-white"
|
||||
><Datetime value={campaign.createdAt} /></span
|
||||
>
|
||||
|
||||
{#if !isSelfManaged}
|
||||
<span class="text-grayblue-dark font-medium">Delivery start:</span>
|
||||
<span class="text-pc-darkblue"><Datetime value={campaign.sendStartAt} /></span>
|
||||
<span class="text-pc-darkblue dark:text-white"
|
||||
><Datetime value={campaign.sendStartAt} /></span
|
||||
>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Delivery finish:</span>
|
||||
<span class="text-pc-darkblue"><Datetime value={campaign.sendEndAt} /></span>
|
||||
<span class="text-pc-darkblue dark:text-white"
|
||||
><Datetime value={campaign.sendEndAt} /></span
|
||||
>
|
||||
{/if}
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Close At:</span>
|
||||
<span class="text-pc-darkblue"><Datetime value={campaign.closeAt} /></span>
|
||||
<span class="text-pc-darkblue dark:text-white"
|
||||
><Datetime value={campaign.closeAt} /></span
|
||||
>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Closed:</span>
|
||||
<span class="text-pc-darkblue"><Datetime value={campaign.closedAt} /></span>
|
||||
<span class="text-pc-darkblue dark:text-white"
|
||||
><Datetime value={campaign.closedAt} /></span
|
||||
>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Anonymize At:</span>
|
||||
<span class="text-pc-darkblue"><Datetime value={campaign.anonymizeAt} /></span>
|
||||
<span class="text-pc-darkblue dark:text-white"
|
||||
><Datetime value={campaign.anonymizeAt} /></span
|
||||
>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Anonymized:</span>
|
||||
<span class="text-pc-darkblue"><Datetime value={campaign.anonymizedAt} /></span>
|
||||
<span class="text-pc-darkblue dark:text-white"
|
||||
><Datetime value={campaign.anonymizedAt} /></span
|
||||
>
|
||||
</div>
|
||||
|
||||
{#if campaign.constraintWeekDays}
|
||||
<div class="bg-white py-6 rounded-lg">
|
||||
<div class="py-6 rounded-lg">
|
||||
<div class="flex gap-8">
|
||||
<!-- Days Schedule -->
|
||||
<div class="flex-1">
|
||||
@@ -1244,8 +1265,10 @@
|
||||
<!-- Second Row: Schedule Constraints and Actions -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<div></div>
|
||||
<div class="bg-white p-6 rounded-lg">
|
||||
<h3 class="text-xl font-semibold text-pc-darkblue mb-4 border-b pb-2">Actions</h3>
|
||||
<div class="p-6 rounded-lg">
|
||||
<h3 class="text-xl font-semibold text-pc-darkblue dark:text-white mb-4 border-b pb-2">
|
||||
Actions
|
||||
</h3>
|
||||
<div class="space-y-4">
|
||||
<!-- Action Buttons -->
|
||||
<div class="flex flex-wrap gap-3">
|
||||
@@ -1292,20 +1315,10 @@
|
||||
<!-- Upload Section -->
|
||||
<div class="border-t pt-4">
|
||||
<div>
|
||||
<label
|
||||
for="reported-csv-upload"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
<FileField accept=".csv" on:change={onUploadReportedCSV}>
|
||||
Upload Reported CSV
|
||||
</label>
|
||||
<input
|
||||
type="file"
|
||||
id="reported-csv-upload"
|
||||
accept=".csv"
|
||||
on:change={onUploadReportedCSV}
|
||||
class="block w-full text-sm text-gray-900 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-reported file:text-white hover:file:opacity-80"
|
||||
/>
|
||||
<p class="mt-1 text-xs text-gray-500">
|
||||
</FileField>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
CSV format: "Reported by" (email), "Date reported(UTC+02:00)"
|
||||
</p>
|
||||
</div>
|
||||
@@ -1551,15 +1564,17 @@
|
||||
>
|
||||
<div class="space-y-6">
|
||||
<!-- Full-width Landing Page Flow -->
|
||||
<div class="bg-white p-6 rounded-lg">
|
||||
<h3 class="text-xl font-semibold text-pc-darkblue mb-4 border-b pb-2">Phishing Flow</h3>
|
||||
<div class="p-6 rounded-lg">
|
||||
<h3 class="text-xl font-semibold text-pc-darkblue dark:text-white mb-4 border-b pb-2">
|
||||
Phishing Flow
|
||||
</h3>
|
||||
|
||||
<!-- Enhanced Flow Visualization -->
|
||||
<div class="flex items-center justify-center mb-6 text-sm">
|
||||
<div class="flex items-center flex-wrap justify-center gap-2">
|
||||
<!-- First block is always the delivery method -->
|
||||
<div class="text-center px-3 py-2 bg-pc-lightblue rounded">
|
||||
<div class="font-medium">
|
||||
<div class="text-center px-3 py-2 bg-pc-lightblue dark:bg-blue-600 rounded">
|
||||
<div class="font-medium text-gray-800 dark:text-white">
|
||||
{#if template.email}
|
||||
Email
|
||||
{:else}
|
||||
@@ -1575,8 +1590,8 @@
|
||||
|
||||
<!-- Before Landing -->
|
||||
{#if template.beforeLandingPage}
|
||||
<div class="text-center px-3 py-2 bg-pc-lightblue rounded">
|
||||
<div class="font-medium">Before Landing</div>
|
||||
<div class="text-center px-3 py-2 bg-pc-lightblue dark:bg-blue-600 rounded">
|
||||
<div class="font-medium text-gray-800 dark:text-white">Before Landing</div>
|
||||
</div>
|
||||
<!-- Only show arrow if there's a next step -->
|
||||
{#if template.landingPage}
|
||||
@@ -1586,8 +1601,8 @@
|
||||
|
||||
<!-- Main Landing -->
|
||||
{#if template.landingPage}
|
||||
<div class="text-center px-3 py-2 bg-pc-lightblue rounded">
|
||||
<div class="font-medium">Main Landing</div>
|
||||
<div class="text-center px-3 py-2 bg-pc-lightblue dark:bg-blue-600 rounded">
|
||||
<div class="font-medium text-gray-800 dark:text-white">Main Landing</div>
|
||||
</div>
|
||||
<!-- Only show arrow if there's a next step -->
|
||||
{#if template.afterLandingPage || template.afterLandingPageRedirectURL}
|
||||
@@ -1597,14 +1612,14 @@
|
||||
|
||||
<!-- After Landing or Redirect -->
|
||||
{#if template.afterLandingPage}
|
||||
<div class="text-center px-3 py-2 bg-pc-lightblue rounded">
|
||||
<div class="font-medium">After Landing</div>
|
||||
<div class="text-center px-3 py-2 bg-pc-lightblue dark:bg-blue-600 rounded">
|
||||
<div class="font-medium text-gray-800 dark:text-white">After Landing</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if template.afterLandingPageRedirectURL}
|
||||
<div class="mx-2">→</div>
|
||||
<div class="text-center px-3 py-2 bg-pc-lightorange rounded">
|
||||
<div class="font-medium">Redirect</div>
|
||||
<div class="text-center px-3 py-2 bg-pc-lightorange dark:bg-orange-600 rounded">
|
||||
<div class="font-medium text-gray-800 dark:text-white">Redirect</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1614,22 +1629,26 @@
|
||||
<!-- Basic Info and Email Config -->
|
||||
<div class="grid grid-cols-2 gap-6">
|
||||
<!-- Basic Info Section -->
|
||||
<div class="bg-white p-6 rounded-lg">
|
||||
<h3 class="text-xl font-semibold text-pc-darkblue mb-4 border-b pb-2">
|
||||
<div class="p-6 rounded-lg">
|
||||
<h3 class="text-xl font-semibold text-pc-darkblue dark:text-white mb-4 border-b pb-2">
|
||||
Basic Information
|
||||
</h3>
|
||||
<div class="grid grid-cols-[120px_1fr] gap-y-3">
|
||||
<span class="text-grayblue-dark font-medium">Name:</span>
|
||||
<span class="text-pc-darkblue">{template.name ?? ''}</span>
|
||||
<span class="text-pc-darkblue dark:text-white">{template.name ?? ''}</span>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Query Key:</span>
|
||||
<span class="text-pc-darkblue">{template.urlIdentifier?.name ?? ''}</span>
|
||||
<span class="text-pc-darkblue dark:text-white"
|
||||
>{template.urlIdentifier?.name ?? ''}</span
|
||||
>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">State Key:</span>
|
||||
<span class="text-pc-darkblue">{template.stateIdentifier?.name ?? ''}</span>
|
||||
<span class="text-pc-darkblue dark:text-white"
|
||||
>{template.stateIdentifier?.name ?? ''}</span
|
||||
>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Delivery :</span>
|
||||
<span class="text-pc-darkblue">
|
||||
<span class="text-pc-darkblue dark:text-white">
|
||||
{#if template.email}
|
||||
Email ({template.email.name ?? ''})
|
||||
{:else}
|
||||
@@ -1638,38 +1657,49 @@
|
||||
</span>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Before Page:</span>
|
||||
<span class="text-pc-darkblue">{template.beforeLandingPage?.name ?? ''}</span>
|
||||
<span class="text-pc-darkblue dark:text-white"
|
||||
>{template.beforeLandingPage?.name ?? ''}</span
|
||||
>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Main Page:</span>
|
||||
<span class="text-pc-darkblue">{template.landingPage?.name ?? ''}</span>
|
||||
<span class="text-pc-darkblue dark:text-white">{template.landingPage?.name ?? ''}</span>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">After Page:</span>
|
||||
<span class="text-pc-darkblue">{template.afterLandingPage?.name ?? ''}</span>
|
||||
<span class="text-pc-darkblue dark:text-white"
|
||||
>{template.afterLandingPage?.name ?? ''}</span
|
||||
>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Redirect URL:</span>
|
||||
<span class="text-pc-darkblue">{template.afterLandingPageRedirectURL ?? ''}</span>
|
||||
<span class="text-pc-darkblue dark:text-white"
|
||||
>{template.afterLandingPageRedirectURL ?? ''}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Email Configuration -->
|
||||
{#if template.email}
|
||||
<div class="bg-white p-6 rounded-lg">
|
||||
<h3 class="text-xl font-semibold text-pc-darkblue mb-4 border-b pb-2">Email</h3>
|
||||
<div class="p-6 rounded-lg">
|
||||
<h3 class="text-xl font-semibold text-pc-darkblue dark:text-white mb-4 border-b pb-2">
|
||||
Email
|
||||
</h3>
|
||||
<div class="grid grid-cols-[120px_1fr] gap-y-3">
|
||||
<span class="text-grayblue-dark font-medium">Name:</span>
|
||||
<span class="text-pc-darkblue">{template.email.name}</span>
|
||||
<span class="text-pc-darkblue dark:text-white">{template.email.name}</span>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Envelope:</span>
|
||||
<span class="text-pc-darkblue">{template.email.mailEnvelopeFrom}</span>
|
||||
<span class="text-pc-darkblue dark:text-white">{template.email.mailEnvelopeFrom}</span
|
||||
>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">From:</span>
|
||||
<span class="text-pc-darkblue">{template.email.mailHeaderFrom}</span>
|
||||
<span class="text-pc-darkblue dark:text-white">{template.email.mailHeaderFrom}</span>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Subject:</span>
|
||||
<span class="text-pc-darkblue">{template.email.mailHeaderSubject}</span>
|
||||
<span class="text-pc-darkblue dark:text-white"
|
||||
>{template.email.mailHeaderSubject}</span
|
||||
>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Tracking:</span>
|
||||
<span class="text-pc-darkblue"
|
||||
<span class="text-pc-darkblue dark:text-white"
|
||||
>{template.email.addTrackingPixel ? 'Enabled' : 'Disabled'}</span
|
||||
>
|
||||
</div>
|
||||
@@ -1680,28 +1710,32 @@
|
||||
<!-- Domain and SMTP Config -->
|
||||
<div class="grid grid-cols-2 gap-6">
|
||||
<!-- Domain Configuration -->
|
||||
<div class="bg-white p-6 rounded-lg">
|
||||
<h3 class="text-xl font-semibold text-pc-darkblue mb-4 border-b pb-2">Domain</h3>
|
||||
<div class="p-6 rounded-lg">
|
||||
<h3 class="text-xl font-semibold text-pc-darkblue dark:text-white mb-4 border-b pb-2">
|
||||
Domain
|
||||
</h3>
|
||||
<div class="grid grid-cols-[120px_1fr] gap-y-3">
|
||||
<span class="text-grayblue-dark font-medium">Host Site:</span>
|
||||
<span class="text-pc-darkblue">{template.domain.hostWebsite ? 'Yes' : 'No'}</span>
|
||||
<span class="text-pc-darkblue dark:text-white"
|
||||
>{template.domain.hostWebsite ? 'Yes' : 'No'}</span
|
||||
>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Domain:</span>
|
||||
<span class="text-pc-darkblue">
|
||||
<span class="text-pc-darkblue dark:text-white">
|
||||
<a
|
||||
href="https://{template.domain?.name}"
|
||||
target="_blank"
|
||||
class="text-pc-blue hover:underline"
|
||||
class="text-cta-blue dark:text-white hover:underline"
|
||||
>
|
||||
{template.domain?.name}
|
||||
</a>
|
||||
</span>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">URL Path:</span>
|
||||
<span class="text-pc-darkblue">{template.urlPath}</span>
|
||||
<span class="text-pc-darkblue dark:text-white">{template.urlPath}</span>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">TLS:</span>
|
||||
<span class="text-pc-darkblue">
|
||||
<span class="text-pc-darkblue dark:text-white">
|
||||
{template.domain.managedTLS ? 'Managed' : template.domain.ownManagedTLS ? 'Own' : ''}
|
||||
</span>
|
||||
</div>
|
||||
@@ -1709,33 +1743,39 @@
|
||||
|
||||
<!-- SMTP/API Configuration -->
|
||||
{#if template.smtpConfiguration || template.apiSender}
|
||||
<div class="bg-white p-6 rounded-lg">
|
||||
<h3 class="text-xl font-semibold text-pc-darkblue mb-4 border-b pb-2">
|
||||
<div class="p-6 rounded-lg">
|
||||
<h3 class="text-xl font-semibold text-pc-darkblue dark:text-white mb-4 border-b pb-2">
|
||||
{template.smtpConfiguration ? 'Email SMTP' : 'API Sender'}
|
||||
</h3>
|
||||
<div class="grid grid-cols-[120px_1fr] gap-y-3">
|
||||
{#if template.smtpConfiguration}
|
||||
<span class="text-grayblue-dark font-medium">Name:</span>
|
||||
<span class="text-pc-darkblue">{template.smtpConfiguration.name}</span>
|
||||
<span class="text-pc-darkblue dark:text-white"
|
||||
>{template.smtpConfiguration.name}</span
|
||||
>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Host:</span>
|
||||
<span class="text-pc-darkblue">{template.smtpConfiguration.host}</span>
|
||||
<span class="text-pc-darkblue dark:text-white"
|
||||
>{template.smtpConfiguration.host}</span
|
||||
>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Port:</span>
|
||||
<span class="text-pc-darkblue">{template.smtpConfiguration.port}</span>
|
||||
<span class="text-pc-darkblue dark:text-white"
|
||||
>{template.smtpConfiguration.port}</span
|
||||
>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Username:</span>
|
||||
<span class="text-pc-darkblue">
|
||||
<span class="text-pc-darkblue dark:text-white">
|
||||
{template.smtpConfiguration.username || 'Not configured'}
|
||||
</span>
|
||||
|
||||
<span class="text-grayblue-dark font-medium">Allow insecure: </span>
|
||||
<span class="text-pc-darkblue">
|
||||
<span class="text-pc-darkblue dark:text-white">
|
||||
{!template.smtpConfiguration.ignoreCertErrors ? 'Disabled' : 'Enabled'}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="text-grayblue-dark font-medium">API Sender:</span>
|
||||
<span class="text-pc-darkblue">{template.apiSender?.name}</span>
|
||||
<span class="text-pc-darkblue dark:text-white">{template.apiSender?.name}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1778,7 +1818,7 @@
|
||||
? `Are you sure you want to send the campaign ${getMessageType()} again to:`
|
||||
: `Are you sure you want to send the campaign ${getMessageType()} to:`}
|
||||
</p>
|
||||
<div class="bg-gray-50 p-3 rounded mb-4">
|
||||
<div class="bg-gray-50 dark:bg-gray-700 p-3 rounded mb-4">
|
||||
<p class="font-medium">{sendMessageRecipient.name}</p>
|
||||
<p class="text-gray-600">{sendMessageRecipient.email}</p>
|
||||
<p class="text-xs text-blue-600 mt-1">
|
||||
|
||||
@@ -72,6 +72,8 @@
|
||||
// Toggle for including test campaigns
|
||||
let includeTestCampaigns = false;
|
||||
|
||||
// Use consistent colors with campaign detail page - these are already well-suited for both light and dark modes
|
||||
|
||||
// Handler for when toggle changes
|
||||
const handleToggleChange = async () => {
|
||||
// Wait for binding to update
|
||||
@@ -292,7 +294,7 @@
|
||||
<main>
|
||||
<div class="flex justify-between">
|
||||
<Headline>Dashboard</Headline>
|
||||
<div class="flex gap-4 items-baseline">
|
||||
<div class="flex gap-4 items-center">
|
||||
<CheckboxField
|
||||
bind:value={includeTestCampaigns}
|
||||
on:change={handleToggleChange}
|
||||
@@ -449,13 +451,13 @@
|
||||
<StatsCard
|
||||
title="Completed Campaigns"
|
||||
value={finished}
|
||||
borderColor="border-campaign-completed"
|
||||
iconColor="text-campaign-completed"
|
||||
borderColor="border-message-read"
|
||||
iconColor="text-message-read"
|
||||
>
|
||||
<svg
|
||||
slot="icon"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5 ml-2 text-campaign-completed"
|
||||
class="h-5 w-5 ml-2 text-message-read"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@@ -474,13 +476,13 @@
|
||||
<StatsCard
|
||||
title="Repeat Offenders"
|
||||
value={repeatOffenders}
|
||||
borderColor="border-repeart-submissions"
|
||||
iconColor="text-repeart-submissions"
|
||||
borderColor="border-submitted-data"
|
||||
iconColor="text-submitted-data"
|
||||
>
|
||||
<svg
|
||||
slot="icon"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5 ml-2 text-repeart-submissions"
|
||||
class="h-5 w-5 ml-2 text-submitted-data"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
|
||||
@@ -592,8 +592,10 @@
|
||||
<FormColumns>
|
||||
<FormColumn>
|
||||
<!-- Domain Information Section -->
|
||||
<div class="mb-6 pt-4 pb-2 border-b border-gray-200 w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue mb-3">Domain Information</h3>
|
||||
<div class="mb-6 pt-4 pb-2 border-b border-gray-200 dark:border-gray-600 w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue dark:text-white mb-3">
|
||||
Domain Information
|
||||
</h3>
|
||||
<div class="space-y-6">
|
||||
<TextField
|
||||
minLength={3}
|
||||
@@ -628,9 +630,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TLS Configuration Section -->
|
||||
<div class="mb-6 pt-4 pb-2 w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue mb-3">TLS Configuration</h3>
|
||||
<!-- SSL Configuration Section -->
|
||||
<div class="pt-4 pb-2 w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue dark:text-white mb-3">
|
||||
SSL Configuration
|
||||
</h3>
|
||||
<div class="space-y-6">
|
||||
<SelectSquare
|
||||
label="Managed TLS"
|
||||
|
||||
@@ -139,16 +139,22 @@
|
||||
|
||||
<HeadTitle title="Setup" />
|
||||
|
||||
<div class="inset-0 z-50 min-h-screen">
|
||||
<div
|
||||
class="inset-0 z-50 min-h-screen bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 transition-colors duration-200"
|
||||
>
|
||||
<div class="flex flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-center">
|
||||
<img src="/logo-blue.svg" class="w-48" alt="Phishing Club" />
|
||||
</div>
|
||||
<p class="mt-2 text-center text-sm text-gray-600">
|
||||
<p
|
||||
class="mt-2 text-center text-sm text-gray-600 dark:text-gray-300 transition-colors duration-200"
|
||||
>
|
||||
Complete the setup to get started with Phishing Club
|
||||
</p>
|
||||
|
||||
<div class="sm:mx-auto sm:max-w-2xl mt-8">
|
||||
<div
|
||||
class="sm:mx-auto sm:max-w-2xl mt-8 bg-white dark:bg-gray-800 rounded-lg shadow-lg dark:shadow-gray-900/50 transition-colors duration-200"
|
||||
>
|
||||
<div class="flex justify-between items-center mb-8 w-full px-4">
|
||||
{#each steps as step, index}
|
||||
<div class="flex flex-col items-center w-32">
|
||||
@@ -263,7 +269,7 @@
|
||||
{#if currentStep > 1}
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
class="inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
on:click={previousStep}
|
||||
>
|
||||
<svg
|
||||
|
||||
@@ -208,7 +208,7 @@
|
||||
|
||||
<HeadTitle title="Sign in" />
|
||||
<main
|
||||
class="h-screen grid-cols-1 grid md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-2 2xl:grid-cols-2"
|
||||
class="h-screen grid-cols-1 grid md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-2 2xl:grid-cols-2 bg-white dark:bg-gray-900 transition-colors duration-200"
|
||||
>
|
||||
<div class="flex items-center justify-center h-full">
|
||||
<img
|
||||
@@ -221,7 +221,7 @@
|
||||
>
|
||||
<div class="flex flex-col items-center justify-center w-full p-4">
|
||||
<h1
|
||||
class="text-4xl md:text-5xl lg:text-5xl xl:text-5xl 2xl:text-5xl font-titilium font-bold uppercase text-pc-darkblue text-center"
|
||||
class="text-4xl md:text-5xl lg:text-5xl xl:text-5xl 2xl:text-5xl font-titilium font-bold uppercase text-pc-darkblue dark:text-white text-center transition-colors duration-200"
|
||||
>
|
||||
Please sign in
|
||||
</h1>
|
||||
@@ -230,7 +230,7 @@
|
||||
<div class="flex flex-col items-center justify-center w-full p-px md:p-px lg:p-4">
|
||||
{#if loginError}
|
||||
<div
|
||||
class="flex justify-center w-9/10 bg-message-red border-pc-red border text-center py-4 font-titilium"
|
||||
class="flex justify-center w-9/10 bg-message-red dark:bg-red-900 border-pc-red dark:border-red-700 border text-center py-4 font-titilium text-gray-900 dark:text-white transition-colors duration-200"
|
||||
>
|
||||
{loginError}
|
||||
</div>
|
||||
@@ -248,7 +248,9 @@
|
||||
submitOnEnter
|
||||
/>
|
||||
<div class="flex flex-col w-full p-4 h-24">
|
||||
<label for="Password" class="text-md font-semibold font-titilium text-pc-darkblue"
|
||||
<label
|
||||
for="Password"
|
||||
class="text-md font-semibold font-titilium text-pc-darkblue dark:text-white transition-colors duration-200"
|
||||
>Password</label
|
||||
>
|
||||
<div class="relative flex items-center justify-end">
|
||||
@@ -259,9 +261,17 @@
|
||||
on:click={handleClick}
|
||||
>
|
||||
{#if isPasswordVisible}
|
||||
<img src="/view.svg" alt="view" />
|
||||
<img
|
||||
src="/view.svg"
|
||||
alt="view"
|
||||
class="dark:filter dark:brightness-0 dark:invert transition-all duration-200"
|
||||
/>
|
||||
{:else}
|
||||
<img src="/toggle-view.svg" alt="toggle view" />
|
||||
<img
|
||||
src="/toggle-view.svg"
|
||||
alt="toggle view"
|
||||
class="dark:filter dark:brightness-0 dark:invert transition-all duration-200"
|
||||
/>
|
||||
{/if}
|
||||
</button>
|
||||
<input
|
||||
@@ -281,14 +291,18 @@
|
||||
type={inputType}
|
||||
id="Password"
|
||||
name="Password"
|
||||
class="relative w-full p-2 rounded bg-pc-lightblue focus:outline-none focus:ring-0 focus:border-cta-blue focus:border-2"
|
||||
class="relative w-full p-2 rounded bg-pc-lightblue dark:bg-gray-700 focus:outline-none focus:ring-0 focus:border-cta-blue dark:focus:border-blue-400 focus:border-2 text-gray-900 dark:text-white transition-colors duration-200"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<CTAbutton disabled={isSubmitting} />
|
||||
{#if isSSOEnabled}
|
||||
<div class="absolute bottom-12">
|
||||
<div class="text-center font-bold">SSO</div>
|
||||
<div
|
||||
class="text-center font-bold text-gray-900 dark:text-white transition-colors duration-200"
|
||||
>
|
||||
SSO
|
||||
</div>
|
||||
<a href="/api/v1/sso/entra-id/login">
|
||||
<img src="/ms-login-light.svg" alt="Login with Microsoft" />
|
||||
</a>
|
||||
@@ -303,15 +317,22 @@
|
||||
>
|
||||
<FormColumns>
|
||||
<FormColumn>
|
||||
<p class="text-center text-lg">Enter the MFA code from your authenticator app</p>
|
||||
<p
|
||||
class="text-center text-lg text-gray-900 dark:text-white transition-colors duration-200"
|
||||
>
|
||||
Enter the MFA code from your authenticator app
|
||||
</p>
|
||||
<Input fieldName={'MFA Code'} type="text" bind:value={formValues.mfaTOTP} />
|
||||
|
||||
<div class="flex flex-col">
|
||||
<p class="text-sm">
|
||||
<p
|
||||
class="text-sm text-gray-700 dark:text-gray-300 transition-colors duration-200"
|
||||
>
|
||||
Forgot your MFA code? Recover your code <button
|
||||
type="button"
|
||||
on:click={showMFARecoveryModal}
|
||||
class="text-cta-blue underline">here</button
|
||||
class="text-cta-blue dark:text-blue-400 underline hover:text-blue-700 dark:hover:text-blue-300 transition-colors duration-200"
|
||||
>here</button
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
@@ -319,7 +340,7 @@
|
||||
</FormColumns>
|
||||
<FormError message={mfaError} />
|
||||
<div
|
||||
class="row-start-7 py-4 row-span-2 col-start-1 col-span-3 border-t-2 w-full flex flex-row justify-center items-center sm:justify-center md:justify-center lg:justify-end xl:justify-end 2xl:justify-end"
|
||||
class="row-start-7 py-4 row-span-2 col-start-1 col-span-3 border-t-2 dark:border-gray-600 w-full flex flex-row justify-center items-center sm:justify-center md:justify-center lg:justify-end xl:justify-end 2xl:justify-end transition-colors duration-200"
|
||||
>
|
||||
<FormButton>Verify</FormButton>
|
||||
</div>
|
||||
@@ -338,7 +359,11 @@
|
||||
<FormColumns>
|
||||
<FormColumn>
|
||||
<div class="px-16 py-8">
|
||||
<p class="text-center text-lg">Please enter the MFA recovery code</p>
|
||||
<p
|
||||
class="text-center text-lg text-gray-900 dark:text-white transition-colors duration-200"
|
||||
>
|
||||
Please enter the MFA recovery code
|
||||
</p>
|
||||
<Input
|
||||
fieldName={'MFA Recovery Code'}
|
||||
type="text"
|
||||
@@ -350,7 +375,7 @@
|
||||
|
||||
<FormError message={mfaRecoveryLoginError} />
|
||||
<div
|
||||
class="row-start-7 py-4 row-span-2 col-start-1 col-span-3 border-t-2 w-full flex flex-row justify-center items-center sm:justify-center md:justify-center lg:justify-end xl:justify-end 2xl:justify-end"
|
||||
class="row-start-7 py-4 row-span-2 col-start-1 col-span-3 border-t-2 dark:border-gray-600 w-full flex flex-row justify-center items-center sm:justify-center md:justify-center lg:justify-end xl:justify-end 2xl:justify-end transition-colors duration-200"
|
||||
>
|
||||
<FormButton>Verify</FormButton>
|
||||
</div>
|
||||
|
||||
@@ -6,27 +6,29 @@
|
||||
import { globalButtonDisabledAttributes } from '$lib/utils/form.js';
|
||||
import Headline from '$lib/components/Headline.svelte';
|
||||
import TextField from '$lib/components/TextField.svelte';
|
||||
import TableCell from '$lib/components/table/TableCell.svelte';
|
||||
import TableRow from '$lib/components/table/TableRow.svelte';
|
||||
import TableCell from '$lib/components/table/TableCell.svelte';
|
||||
import TableUpdateButton from '$lib/components/table/TableUpdateButton.svelte';
|
||||
import TableDeleteButton from '$lib/components/table/TableDeleteButton2.svelte';
|
||||
import { addToast } from '$lib/store/toast';
|
||||
import FormError from '$lib/components/FormError.svelte';
|
||||
import { addToast } from '$lib/store/toast';
|
||||
import { AppStateService } from '$lib/service/appState';
|
||||
import TableCellAction from '$lib/components/table/TableCellAction.svelte';
|
||||
import TableCellEmpty from '$lib/components/table/TableCellEmpty.svelte';
|
||||
import FormGrid from '$lib/components/FormGrid.svelte';
|
||||
import TableCellAction from '$lib/components/table/TableCellAction.svelte';
|
||||
import Modal from '$lib/components/Modal.svelte';
|
||||
import FormGrid from '$lib/components/FormGrid.svelte';
|
||||
import BigButton from '$lib/components/BigButton.svelte';
|
||||
import FormColumns from '$lib/components/FormColumns.svelte';
|
||||
import FormColumn from '$lib/components/FormColumn.svelte';
|
||||
import FormFooter from '$lib/components/FormFooter.svelte';
|
||||
import Table from '$lib/components/table/Table.svelte';
|
||||
import HeadTitle from '$lib/components/HeadTitle.svelte';
|
||||
import { getModalText } from '$lib/utils/common';
|
||||
import TableCopyButton from '$lib/components/table/TableCopyButton.svelte';
|
||||
import { showIsLoading, hideIsLoading } from '$lib/store/loading.js';
|
||||
import Editor from '$lib/components/editor/Editor.svelte';
|
||||
import TableDropDownEllipsis from '$lib/components/table/TableDropDownEllipsis.svelte';
|
||||
import DeleteAlert from '$lib/components/modal/DeleteAlert.svelte';
|
||||
import Editor from '$lib/components/editor/Editor.svelte';
|
||||
import { fetchAllRows } from '$lib/utils/api-utils';
|
||||
import { BiMap } from '$lib/utils/maps';
|
||||
import AutoRefresh from '$lib/components/AutoRefresh.svelte';
|
||||
|
||||
@@ -361,9 +361,13 @@
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
<!-- Profile Settings -->
|
||||
<div
|
||||
class="bg-white p-6 rounded-lg shadow-sm border border-gray-100 min-h-[300px] flex flex-col"
|
||||
class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-sm dark:shadow-gray-900/50 border border-gray-100 dark:border-gray-700 min-h-[300px] flex flex-col transition-colors duration-200"
|
||||
>
|
||||
<h2 class="text-xl font-semibold text-gray-700 mb-6">Account Details</h2>
|
||||
<h2
|
||||
class="text-xl font-semibold text-gray-700 dark:text-gray-200 mb-6 transition-colors duration-200"
|
||||
>
|
||||
Account Details
|
||||
</h2>
|
||||
<Form
|
||||
fullHeight
|
||||
on:submit={async () => {
|
||||
@@ -402,9 +406,13 @@
|
||||
|
||||
<!-- Password Settings -->
|
||||
<div
|
||||
class="bg-white p-6 rounded-lg shadow-sm border border-gray-100 min-h-[300px] flex flex-col"
|
||||
class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-sm dark:shadow-gray-900/50 border border-gray-100 dark:border-gray-700 min-h-[300px] flex flex-col transition-colors duration-200"
|
||||
>
|
||||
<h2 class="text-xl font-semibold text-gray-700 mb-6">Password Settings</h2>
|
||||
<h2
|
||||
class="text-xl font-semibold text-gray-700 dark:text-gray-200 mb-6 transition-colors duration-200"
|
||||
>
|
||||
Password Settings
|
||||
</h2>
|
||||
{#if !isSSOUser}
|
||||
<Form on:submit={onClickChangePassword}>
|
||||
<div class="flex flex-col h-full">
|
||||
@@ -437,16 +445,22 @@
|
||||
</div>
|
||||
</Form>
|
||||
{:else}
|
||||
<div class="bg-gray-50 p-4 rounded-md text-gray-600">
|
||||
<div
|
||||
class="bg-gray-50 dark:bg-gray-800 p-4 rounded-md text-gray-600 dark:text-gray-300"
|
||||
>
|
||||
Password changes are disabled for SSO users.
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<!-- MFA Section -->
|
||||
<div
|
||||
class="bg-white p-6 rounded-lg shadow-sm border border-gray-100 min-h-[300px] flex flex-col"
|
||||
class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-sm dark:shadow-gray-900/50 border border-gray-100 dark:border-gray-700 min-h-[300px] flex flex-col transition-colors duration-200"
|
||||
>
|
||||
<h2 class="text-xl font-semibold text-gray-700 mb-6">Multi-Factor Authentication</h2>
|
||||
<h2
|
||||
class="text-xl font-semibold text-gray-700 dark:text-gray-200 mb-6 transition-colors duration-200"
|
||||
>
|
||||
Multi-Factor Authentication
|
||||
</h2>
|
||||
{#if !isSSOUser}
|
||||
{#if isMFAEnabled}
|
||||
<div class="flex flex-col h-full pt-5 w-60">
|
||||
@@ -484,7 +498,9 @@
|
||||
</Form>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="bg-gray-50 p-4 rounded-md text-gray-600">
|
||||
<div
|
||||
class="bg-gray-50 dark:bg-gray-800 p-4 rounded-md text-gray-600 dark:text-gray-300"
|
||||
>
|
||||
MFA is managed through your SSO provider.
|
||||
</div>
|
||||
{/if}
|
||||
@@ -492,9 +508,13 @@
|
||||
|
||||
<!-- API Key Section -->
|
||||
<div
|
||||
class="bg-white p-6 rounded-lg shadow-sm border border-gray-100 min-h-[300px] flex flex-col"
|
||||
class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-sm dark:shadow-gray-900/50 border border-gray-100 dark:border-gray-700 min-h-[300px] flex flex-col transition-colors duration-200"
|
||||
>
|
||||
<h2 class="text-xl font-semibold text-gray-700 mb-6">API Access</h2>
|
||||
<h2
|
||||
class="text-xl font-semibold text-gray-700 dark:text-gray-200 mb-6 transition-colors duration-200"
|
||||
>
|
||||
API Access
|
||||
</h2>
|
||||
<Form fullHeight on:submit={onClickCreateAPIKey}>
|
||||
<div class="flex flex-col h-full">
|
||||
{#if !!apiKey.length}
|
||||
@@ -508,7 +528,9 @@
|
||||
<Button size={'large'} on:click={openShowRemoveAPIKeyModal}>Delete Key</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="bg-gray-50 p-4 rounded-md text-gray-600 mb-4">
|
||||
<div
|
||||
class="bg-gray-50 dark:bg-gray-800 p-4 rounded-md text-gray-600 dark:text-gray-300 mb-4"
|
||||
>
|
||||
No API key currently exists.
|
||||
</div>
|
||||
<div class="mt-auto pt-4">
|
||||
@@ -578,9 +600,11 @@
|
||||
</div>
|
||||
|
||||
<!-- Alternative Method -->
|
||||
<div class="border-gray-200 pt-4">
|
||||
<h3 class="text-lg font-medium text-gray-700 mb-2">Can't scan the QR code?</h3>
|
||||
<p class="text-gray-600 mb-2">
|
||||
<div class="border-gray-200 dark:border-gray-600 pt-4">
|
||||
<h3 class="text-lg font-medium text-gray-700 dark:text-gray-200 mb-2">
|
||||
Can't scan the QR code?
|
||||
</h3>
|
||||
<p class="text-gray-600 dark:text-gray-300 mb-2">
|
||||
Enter this code manually in your authenticator app:
|
||||
</p>
|
||||
<button
|
||||
@@ -594,9 +618,11 @@
|
||||
</div>
|
||||
|
||||
<!-- Step 2 -->
|
||||
<div class="border-t border-gray-200 pt-4">
|
||||
<h3 class="text-xl font-semibold text-gray-700 mb-2">2. Verify Setup</h3>
|
||||
<p class="text-gray-600 mb-4">
|
||||
<div class="border-t border-gray-200 dark:border-gray-600 pt-4">
|
||||
<h3 class="text-xl font-semibold text-gray-700 dark:text-gray-200 mb-2">
|
||||
2. Verify Setup
|
||||
</h3>
|
||||
<p class="text-gray-600 dark:text-gray-300 mb-4">
|
||||
Enter the 6-digit code from your authenticator app:
|
||||
</p>
|
||||
<TextField
|
||||
|
||||
@@ -442,9 +442,13 @@
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-4 gap-8">
|
||||
<!-- SSO Card -->
|
||||
<div
|
||||
class="bg-white p-6 rounded-lg shadow-sm border border-gray-100 min-h-[300px] flex flex-col"
|
||||
class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-sm dark:shadow-gray-900/50 border border-gray-100 dark:border-gray-700 min-h-[300px] flex flex-col transition-colors duration-200"
|
||||
>
|
||||
<h2 class="text-xl font-semibold text-gray-700 mb-6">Single Sign-On</h2>
|
||||
<h2
|
||||
class="text-xl font-semibold text-gray-700 dark:text-gray-200 mb-6 transition-colors duration-200"
|
||||
>
|
||||
Single Sign-On
|
||||
</h2>
|
||||
<div class="flex flex-col h-full pt-4">
|
||||
<div class="bg-gray-50 rounded-md p-3">
|
||||
{#if isSSOEnabled}
|
||||
@@ -476,9 +480,13 @@
|
||||
|
||||
<!-- General Settings Card -->
|
||||
<div
|
||||
class="bg-white p-6 rounded-lg shadow-sm border border-gray-100 min-h-[300px] flex flex-col"
|
||||
class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-sm dark:shadow-gray-900/50 border border-gray-100 dark:border-gray-700 min-h-[300px] flex flex-col transition-colors duration-200"
|
||||
>
|
||||
<h2 class="text-xl font-semibold text-gray-700 mb-6">General Settings</h2>
|
||||
<h2
|
||||
class="text-xl font-semibold text-gray-700 dark:text-gray-200 mb-6 transition-colors duration-200"
|
||||
>
|
||||
General Settings
|
||||
</h2>
|
||||
<Form on:submit={onClickUpdateSettings} fullHeight>
|
||||
<div class="flex flex-col h-full">
|
||||
<div>
|
||||
@@ -504,9 +512,13 @@
|
||||
|
||||
<!-- Logging Card -->
|
||||
<div
|
||||
class="bg-white p-6 rounded-lg shadow-sm border border-gray-100 min-h-[300px] flex flex-col"
|
||||
class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-sm dark:shadow-gray-900/50 border border-gray-100 dark:border-gray-700 min-h-[300px] flex flex-col transition-colors duration-200"
|
||||
>
|
||||
<h2 class="text-xl font-semibold text-gray-700 mb-6">Logging</h2>
|
||||
<h2
|
||||
class="text-xl font-semibold text-gray-700 dark:text-gray-200 mb-6 transition-colors duration-200"
|
||||
>
|
||||
Logging
|
||||
</h2>
|
||||
<Form fullHeight>
|
||||
<div class="flex flex-col h-full">
|
||||
<div>
|
||||
@@ -532,9 +544,13 @@
|
||||
|
||||
<!-- Import Section -->
|
||||
<div
|
||||
class="bg-white p-6 rounded-lg shadow-sm border border-gray-100 min-h-[300px] flex flex-col"
|
||||
class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-sm dark:shadow-gray-900/50 border border-gray-100 dark:border-gray-700 min-h-[300px] flex flex-col transition-colors duration-200"
|
||||
>
|
||||
<h2 class="text-xl font-semibold text-gray-700 mb-6">Import</h2>
|
||||
<h2
|
||||
class="text-xl font-semibold text-gray-700 dark:text-gray-200 mb-6 transition-colors duration-200"
|
||||
>
|
||||
Import
|
||||
</h2>
|
||||
<div class="flex flex-col h-full">
|
||||
<div class="space-y-4">
|
||||
<FileField name="importFile" accept=".zip" on:change={(e) => onSetImportFile(e)}>
|
||||
@@ -549,12 +565,16 @@
|
||||
Import pages and emails as company templates
|
||||
</label>
|
||||
{#if importForCompany}
|
||||
<div class="bg-blue-50 p-3 rounded-md text-sm text-blue-700">
|
||||
<div
|
||||
class="bg-blue-50 dark:bg-blue-900/30 p-3 rounded-md text-sm text-blue-700 dark:text-blue-200 transition-colors duration-200"
|
||||
>
|
||||
<strong>Company Import:</strong><br /> Pages and emails will be imported for this company.
|
||||
Assets will be imported as global/shared resources.
|
||||
</div>
|
||||
{:else}
|
||||
<div class="bg-gray-50 p-3 rounded-md text-sm text-gray-600">
|
||||
<div
|
||||
class="bg-gray-50 dark:bg-gray-700 p-3 rounded-md text-sm text-gray-600 dark:text-gray-300 transition-colors duration-200"
|
||||
>
|
||||
<strong>Global Import:</strong> All templates and assets will be imported as shared
|
||||
resources.
|
||||
</div>
|
||||
@@ -579,31 +599,45 @@
|
||||
|
||||
<!-- Backup Section -->
|
||||
<div
|
||||
class="bg-white p-6 rounded-lg shadow-sm border border-gray-100 min-h-[300px] flex flex-col"
|
||||
class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-sm dark:shadow-gray-900/50 border border-gray-100 dark:border-gray-700 min-h-[300px] flex flex-col transition-colors duration-200"
|
||||
>
|
||||
<h2 class="text-xl font-semibold text-gray-700 mb-6">Backup</h2>
|
||||
<h2
|
||||
class="text-xl font-semibold text-gray-700 dark:text-gray-200 mb-6 transition-colors duration-200"
|
||||
>
|
||||
Backup
|
||||
</h2>
|
||||
<div class="flex flex-col h-full">
|
||||
<div class="space-y-4">
|
||||
<p class="text-gray-600 text-sm">
|
||||
<p class="text-gray-600 dark:text-gray-300 text-sm transition-colors duration-200">
|
||||
Create a backup of database, assets, attachments and certificates.
|
||||
</p>
|
||||
|
||||
{#if availableBackups.length > 0}
|
||||
<div class="bg-gray-50 p-3 rounded-md">
|
||||
<h4 class="font-medium text-gray-900 mb-2">Available:</h4>
|
||||
<div
|
||||
class="bg-gray-50 dark:bg-gray-700 p-3 rounded-md transition-colors duration-200"
|
||||
>
|
||||
<h4
|
||||
class="font-medium text-gray-900 dark:text-gray-100 mb-2 transition-colors duration-200"
|
||||
>
|
||||
Available:
|
||||
</h4>
|
||||
<div class="space-y-3">
|
||||
{#each availableBackups as backup}
|
||||
<div class="flex items-start justify-between gap-4 text-sm">
|
||||
<div class="flex flex-col min-w-0 flex-1">
|
||||
<span class="text-gray-700 text-xs font-medium">
|
||||
<span
|
||||
class="text-gray-700 dark:text-gray-200 text-xs font-medium transition-colors duration-200"
|
||||
>
|
||||
{new Date(backup.createdAt).toLocaleString()}
|
||||
</span>
|
||||
<span class="text-gray-400 text-xs">
|
||||
<span
|
||||
class="text-gray-400 dark:text-gray-400 text-xs transition-colors duration-200"
|
||||
>
|
||||
{(backup.size / 1024 / 1024).toFixed(1)} MB
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
class="px-3 py-1 bg-blue-600 hover:bg-blue-700 text-white text-xs rounded transition-colors flex-shrink-0"
|
||||
class="px-3 py-1 bg-blue-600 hover:bg-blue-700 dark:bg-blue-700 dark:hover:bg-blue-800 text-white text-xs rounded transition-colors flex-shrink-0"
|
||||
on:click={() => downloadBackup(backup.name)}
|
||||
>
|
||||
Download
|
||||
@@ -613,7 +647,9 @@
|
||||
</div>
|
||||
</div>
|
||||
{:else if !isLoadingBackups}
|
||||
<div class="bg-gray-50 p-3 rounded-md text-sm text-gray-600">
|
||||
<div
|
||||
class="bg-gray-50 dark:bg-gray-700 p-3 rounded-md text-sm text-gray-600 dark:text-gray-300 transition-colors duration-200"
|
||||
>
|
||||
No backups available yet.
|
||||
</div>
|
||||
{/if}
|
||||
@@ -631,7 +667,7 @@
|
||||
{#if isImportResultModalVisible && importResult}
|
||||
<Modal headerText="Import Summary" bind:visible={isImportResultModalVisible}>
|
||||
<div
|
||||
class="p-6 max-h-[80vh] overflow-y-auto"
|
||||
class="p-6 max-h-[80vh] overflow-y-auto bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 transition-colors duration-200"
|
||||
on:scroll={() => {}}
|
||||
bind:this={importModalContent}
|
||||
>
|
||||
@@ -640,20 +676,30 @@
|
||||
<div class="grid grid-cols-3 gap-6">
|
||||
<!-- Assets -->
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900 mb-2">Assets (Global/Shared)</h3>
|
||||
<h3
|
||||
class="font-semibold text-gray-900 dark:text-gray-100 mb-2 transition-colors duration-200"
|
||||
>
|
||||
Assets (Global/Shared)
|
||||
</h3>
|
||||
<ul class="space-y-1">
|
||||
<li>Created: {importResult.assets_created}</li>
|
||||
<li>Skipped: {importResult.assets_skipped}</li>
|
||||
<li>Errors: {importResult.assets_errors}</li>
|
||||
</ul>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
<p
|
||||
class="text-xs text-gray-500 dark:text-gray-400 mt-1 transition-colors duration-200"
|
||||
>
|
||||
Assets are always imported as global resources
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Pages -->
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900 mb-2">Pages</h3>
|
||||
<h3
|
||||
class="font-semibold text-gray-900 dark:text-gray-100 mb-2 transition-colors duration-200"
|
||||
>
|
||||
Pages
|
||||
</h3>
|
||||
<ul class="space-y-1">
|
||||
<li>Created: {importResult.pages_created}</li>
|
||||
<li>Updated: {importResult.pages_updated}</li>
|
||||
@@ -664,7 +710,11 @@
|
||||
|
||||
<!-- Emails -->
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900 mb-2">Emails</h3>
|
||||
<h3
|
||||
class="font-semibold text-gray-900 dark:text-gray-100 mb-2 transition-colors duration-200"
|
||||
>
|
||||
Emails
|
||||
</h3>
|
||||
<ul class="space-y-1">
|
||||
<li>Created: {importResult.emails_created}</li>
|
||||
<li>Updated: {importResult.emails_updated}</li>
|
||||
@@ -829,26 +879,34 @@
|
||||
{/if}
|
||||
|
||||
<!-- Version and Update Info -->
|
||||
<div class="mt-8 text-sm text-gray-600 border-t border-gray-100 pt-4">
|
||||
<div
|
||||
class="mt-8 text-sm text-gray-600 dark:text-gray-300 border-t border-gray-100 dark:border-gray-700 pt-4 transition-colors duration-200"
|
||||
>
|
||||
<div class="flex items-center gap-4 flex-wrap">
|
||||
<button
|
||||
on:click|preventDefault={() => onClickCopy('.version-text')}
|
||||
class="flex items-center hover:bg-gray-100 py-2 px-4 rounded-md text-gray-700 transition-colors"
|
||||
class="flex items-center hover:bg-gray-100 dark:hover:bg-gray-700 py-2 px-4 rounded-md text-gray-700 dark:text-gray-300 transition-colors duration-200"
|
||||
>
|
||||
<span class="version-text font-mono">Version: {version}</span>
|
||||
<img class="ml-2 w-4 h-4" src="/icon-copy.svg" alt="copy version" />
|
||||
</button>
|
||||
<span>|</span>
|
||||
{#if updateAvailable}
|
||||
<a href="/settings/update/" class="text-blue-600 hover:underline">Update Available</a>
|
||||
<a
|
||||
href="/settings/update/"
|
||||
class="text-blue-600 dark:text-white hover:underline transition-colors duration-200"
|
||||
>Update Available</a
|
||||
>
|
||||
{:else}
|
||||
<span class="text-gray-500">Up to date</span>
|
||||
<span class="text-gray-500 dark:text-gray-400 transition-colors duration-200"
|
||||
>Up to date</span
|
||||
>
|
||||
{/if}
|
||||
<span>|</span>
|
||||
<button
|
||||
on:click={checkForUpdate}
|
||||
disabled={isCheckingUpdate}
|
||||
class="text-blue-600 hover:underline disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
class="text-blue-600 dark:text-white hover:underline disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200"
|
||||
>
|
||||
{#if isCheckingUpdate}
|
||||
Checking...
|
||||
@@ -857,7 +915,11 @@
|
||||
{/if}
|
||||
</button>
|
||||
<span>|</span>
|
||||
<a href="/licenses.txt" class="text-blue-600 hover:underline">View Licenses</a>
|
||||
<a
|
||||
href="/licenses.txt"
|
||||
class="text-blue-600 dark:text-white hover:underline transition-colors duration-200"
|
||||
>View Licenses</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -481,8 +481,10 @@
|
||||
<FormColumns>
|
||||
<FormColumn>
|
||||
<!-- Basic Configuration Section -->
|
||||
<div class="mb-6 pt-4 pb-2 border-b border-gray-200 w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue mb-3">Basic Configuration</h3>
|
||||
<div class="mb-6 pt-4 pb-2 border-b border-gray-200 dark:border-gray-600 w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue dark:text-white mb-3">
|
||||
Basic Configuration
|
||||
</h3>
|
||||
<div class="space-y-6">
|
||||
<TextField
|
||||
required
|
||||
@@ -510,8 +512,10 @@
|
||||
</div>
|
||||
|
||||
<!-- Authentication Section -->
|
||||
<div class="mb-6 pt-4 pb-2 border-b border-gray-200 w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue mb-3">Authentication</h3>
|
||||
<div class="mb-6 pt-4 pb-2 border-b border-gray-200 dark:border-gray-600 w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue dark:text-white mb-3">
|
||||
Authentication
|
||||
</h3>
|
||||
<div class="space-y-6">
|
||||
<TextField
|
||||
minLength={1}
|
||||
@@ -531,7 +535,9 @@
|
||||
|
||||
<!-- Security Settings -->
|
||||
<div class="pt-4 pb-2 w-full">
|
||||
<h3 class="text-base font-medium text-pc-darkblue mb-3">Security Settings</h3>
|
||||
<h3 class="text-base font-medium text-pc-darkblue dark:text-white mb-3">
|
||||
Security Settings
|
||||
</h3>
|
||||
<div>
|
||||
<SelectSquare
|
||||
label="TLS Certificate Validation"
|
||||
|
||||
@@ -95,6 +95,10 @@ export default {
|
||||
'70vw': '70vw',
|
||||
'80vw': '80vw',
|
||||
'90vw': '90vw'
|
||||
},
|
||||
minWidth: {
|
||||
app: '768px',
|
||||
desktop: '1024px'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user