Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions assets/router/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import ListsView from '../vue/views/ListsView.vue'
import ListSubscribersView from '../vue/views/ListSubscribersView.vue'
import CampaignsView from '../vue/views/CampaignsView.vue'
import CampaignEditView from '../vue/views/CampaignEditView.vue'
import TemplatesView from '../vue/views/TemplatesView.vue'
import TemplateEditView from '../vue/views/TemplateEditView.vue'

export const router = createRouter({
history: createWebHistory(),
Expand All @@ -13,6 +15,9 @@ export const router = createRouter({
{ path: '/subscribers', name: 'subscribers', component: SubscribersView, meta: { title: 'Subscribers' } },
{ path: '/lists', name: 'lists', component: ListsView, meta: { title: 'Lists' } },
{ path: '/campaigns', name: 'campaigns', component: CampaignsView, meta: { title: 'Campaigns' } },
{ path: '/templates', name: 'templates', component: TemplatesView, meta: { title: 'Templates' } },
{ path: '/templates/create', name: 'template-create', component: TemplateEditView, meta: { title: 'Create Template' } },
{ path: '/templates/:templateId/edit', name: 'template-edit', component: TemplateEditView, meta: { title: 'Edit Template' } },
{ path: '/campaigns/create', name: 'campaign-create', component: CampaignEditView, meta: { title: 'Create Campaign' } },
{ path: '/campaigns/:campaignId/edit', name: 'campaign-edit', component: CampaignEditView, meta: { title: 'Edit Campaign' } },
{ path: '/lists/:listId/subscribers', name: 'list-subscribers', component: ListSubscribersView, meta: { title: 'List Subscribers' } },
Expand Down
6 changes: 5 additions & 1 deletion assets/vue/components/base/BaseIcon.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const props = defineProps({
const icons = {
users: `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-users" aria-hidden="true"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path><path d="M16 3.128a4 4 0 0 1 0 7.744"></path><path d="M22 21v-2a4 4 0 0 0-3-3.87"></path><circle cx="9" cy="7" r="4"></circle></svg>`,

addUser: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-user-plus w-4 h-4 mt-0.5 text-emerald-600" aria-hidden="true"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><line x1="19" x2="19" y1="8" y2="14"></line><line x1="22" x2="16" y1="11" y2="11"></line></svg>`,
addUser: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-user-plus w-4 h-4 mt-0.5" aria-hidden="true"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><line x1="19" x2="19" y1="8" y2="14"></line><line x1="22" x2="16" y1="11" y2="11"></line></svg>`,

removeUser: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-user-minus w-4 h-4 mt-0.5 text-red-600" aria-hidden="true"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><line x1="22" x2="16" y1="11" y2="11"></line></svg>`,

Expand Down Expand Up @@ -91,6 +91,10 @@ const icons = {
plugin: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-package w-6 h-6 text-purple-600 dark:text-purple-400" aria-hidden="true"><path d="M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73z"></path><path d="M12 22V12"></path><polyline points="3.29 7 12 12 20.71 7"></polyline><path d="m7.5 4.27 9 5.15"></path></svg>`,

chevronDown: `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down" aria-hidden="true"><path d="m6 9 6 6 6-6"></path></svg>`,

pause: `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="7" y="5" width="3" height="14" rx="1"></rect><rect x="14" y="5" width="3" height="14" rx="1"></rect></svg>`,

start: `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="6 4 20 12 6 20 6 4" rx="1"></polygon></svg>`,
Comment on lines +95 to +97
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Minor SVG quirks in the new pause/start icons.

A couple of small polish items on the new icons:

  • The start polygon has rx="1", but <polygon> doesn't support rx/ry in SVG — it's silently ignored, so the corners won't actually be rounded. If you want rounded tips, either use a <path> with rounded joins (your stroke-linejoin="round" already helps) or just drop the attribute.
  • stroke-width is inconsistent across the two new icons (1.8 for pause, 2.5 for start), and different from the 2 used by every other icon in this registry. Visually they'll look a bit heavier/lighter than their siblings when rendered side-by-side (e.g., in CampaignDirectory.vue where both appear as w-3.5 h-3.5).
🎨 Suggested tweak
-  pause: `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="7" y="5" width="3" height="14" rx="1"></rect><rect x="14" y="5" width="3" height="14" rx="1"></rect></svg>`,
+  pause: `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="7" y="5" width="3" height="14" rx="1"></rect><rect x="14" y="5" width="3" height="14" rx="1"></rect></svg>`,

-  start: `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="6 4 20 12 6 20 6 4" rx="1"></polygon></svg>`,
+  start: `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polygon points="6 4 20 12 6 20 6 4"></polygon></svg>`,

Also noticed the other icons include xmlns and an inline class="lucide …" — not a bug, but worth mirroring if you care about registry consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@assets/vue/components/base/BaseIcon.vue` around lines 95 - 97, The new SVG
entries for the pause and start icons in BaseIcon.vue are inconsistent: remove
the unsupported rx attribute from the start icon's <polygon> (or replace the
polygon with a <path> if you want rounded tips) and normalize stroke-width to
match the registry (use stroke-width="2" for both the pause and start entries);
optionally add the same xmlns and class="lucide …" attributes present on other
icons for consistency. Ensure you update the 'start' and 'pause' string values
accordingly and keep stroke-linejoin/stroke-linecap as-is if you rely on rounded
joins.

};

const svg = computed(() => icons[props.name] || "");
Expand Down
37 changes: 25 additions & 12 deletions assets/vue/components/campaigns/CampaignDirectory.vue
Original file line number Diff line number Diff line change
Expand Up @@ -90,54 +90,60 @@
<button
v-if="campaign.statusKey === 'draft'"
type="button"
class="px-2.5 py-1 text-xs font-medium text-rose-700 bg-rose-50 border border-rose-200 rounded-md hover:bg-rose-100 disabled:opacity-50"
class="inline-flex items-center gap-1 px-2.5 py-1.5 text-xs font-medium rounded-md border border-red-200 text-red-600 hover:bg-red-50 transition-colors disabled:opacity-50"
:disabled="isActionLoading(campaign.id)"
@click="handleDelete(campaign)"
>
<BaseIcon name="delete" class="w-3.5 h-3.5" />
Delete
</button>
<button
v-else-if="campaign.statusKey === 'active'"
type="button"
class="px-2.5 py-1 text-xs font-medium text-rose-700 bg-rose-50 border border-rose-200 rounded-md hover:bg-rose-100 disabled:opacity-50"
class="inline-flex items-center gap-1 px-2.5 py-1.5 text-xs font-medium rounded-md border border-red-200 text-red-600 hover:bg-red-50 transition-colors disabled:opacity-50"
:disabled="isActionLoading(campaign.id)"
@click="handleSuspend(campaign.id)"
>
<BaseIcon name="pause" class="w-3.5 h-3.5" />
Suspend
</button>
<button
v-else
type="button"
class="px-2.5 py-1 text-xs font-medium text-amber-700 bg-amber-50 border border-amber-200 rounded-md hover:bg-amber-100 disabled:opacity-50"
class="inline-flex items-center gap-1 px-2.5 py-1.5 text-xs font-medium rounded-md border border-amber-200 text-amber-600 hover:bg-amber-50 transition-colors disabled:opacity-50"
:disabled="isActionLoading(campaign.id)"
@click="handleRequeue(campaign.id)"
>
<BaseIcon name="start" class="w-3.5 h-3.5" />
Requeue
</button>
<button
v-if="campaign.statusKey === 'sent'"
type="button"
class="px-2.5 py-1 text-xs font-medium text-slate-700 bg-slate-100 border border-slate-200 rounded-md hover:bg-slate-200 disabled:opacity-50"
class="inline-flex items-center gap-1 px-2.5 py-1.5 text-xs font-medium rounded-md border border-green-200 text-green-700 hover:bg-green-50 transition-colors"
:disabled="isActionLoading(campaign.id)"
@click="handleCopyToDraft(campaign.id)"
>
<BaseIcon name="copy" class="w-3.5 h-3.5" />
Copy to draft
</button>
<button
v-if="campaign.statusKey === 'draft'"
type="button"
class="px-2.5 py-1 text-xs font-medium text-emerald-700 bg-emerald-50 border border-emerald-200 rounded-md hover:bg-emerald-100 disabled:opacity-50"
class="inline-flex items-center gap-1 px-2.5 py-1.5 text-xs font-medium rounded-md border border-slate-200 text-slate-700 hover:bg-slate-50 transition-colors disabled:opacity-50"
:disabled="isActionLoading(campaign.id)"
@click="handleEdit(campaign.id)"
>
<BaseIcon name="edit" class="w-3.5 h-3.5" />
Edit
</button>
<button
type="button"
class="px-2.5 py-1 text-xs font-medium text-blue-700 bg-blue-50 border border-blue-200 rounded-md hover:bg-blue-100 disabled:opacity-50"
class="inline-flex items-center gap-1 px-2.5 py-1.5 text-xs font-medium rounded-md border border-slate-200 text-slate-700 hover:bg-slate-50 transition-colors"
:disabled="isActionLoading(campaign.id)"
@click="handleView(campaign.id)"
>
<BaseIcon name="eye" class="w-3.5 h-3.5" />
View
</button>
</div>
Expand Down Expand Up @@ -224,54 +230,60 @@
<button
v-if="campaign.statusKey === 'draft'"
type="button"
class="px-2.5 py-1 text-xs font-medium text-rose-700 bg-rose-50 border border-rose-200 rounded-md hover:bg-rose-100 disabled:opacity-50"
class="inline-flex items-center gap-1 px-2.5 py-1.5 text-xs font-medium rounded-md border border-red-200 text-red-600 hover:bg-red-50 transition-colors disabled:opacity-50"
:disabled="isActionLoading(campaign.id)"
@click="handleDelete(campaign)"
>
<BaseIcon name="delete" class="w-3.5 h-3.5" />
Delete
</button>
<button
v-else-if="campaign.statusKey === 'active'"
type="button"
class="px-2.5 py-1 text-xs font-medium text-rose-700 bg-rose-50 border border-rose-200 rounded-md hover:bg-rose-100 disabled:opacity-50"
class="inline-flex items-center gap-1 px-2.5 py-1.5 text-xs font-medium rounded-md border border-red-200 text-red-600 hover:bg-red-50 transition-colors disabled:opacity-50"
:disabled="isActionLoading(campaign.id)"
@click="handleSuspend(campaign.id)"
>
<BaseIcon name="pause" class="w-3.5 h-3.5" />
Suspend
</button>
<button
v-else
type="button"
class="px-2.5 py-1 text-xs font-medium text-amber-700 bg-amber-50 border border-amber-200 rounded-md hover:bg-amber-100 disabled:opacity-50"
class="inline-flex items-center gap-1 px-2.5 py-1.5 text-xs font-medium rounded-md border border-amber-200 text-amber-600 hover:bg-amber-50 transition-colors disabled:opacity-50"
:disabled="isActionLoading(campaign.id)"
@click="handleRequeue(campaign.id)"
>
<BaseIcon name="start" class="w-3.5 h-3.5" />
Requeue
</button>
<button
v-if="campaign.statusKey === 'sent'"
type="button"
class="px-2.5 py-1 text-xs font-medium text-slate-700 bg-slate-100 border border-slate-200 rounded-md hover:bg-slate-200 disabled:opacity-50"
class="inline-flex items-center gap-1 px-2.5 py-1.5 text-xs font-medium rounded-md border border-green-200 text-green-700 hover:bg-green-50 transition-colors disabled:opacity-50"
:disabled="isActionLoading(campaign.id)"
@click="handleCopyToDraft(campaign.id)"
>
<BaseIcon name="copy" class="w-3.5 h-3.5" />
Copy to draft
</button>
<button
v-if="campaign.statusKey === 'draft'"
type="button"
class="px-2.5 py-1 text-xs font-medium text-emerald-700 bg-emerald-50 border border-emerald-200 rounded-md hover:bg-emerald-100 disabled:opacity-50"
class="inline-flex items-center gap-1 px-2.5 py-1.5 text-xs font-medium rounded-md border border-slate-200 text-slate-700 hover:bg-slate-50 transition-colors disabled:opacity-50"
:disabled="isActionLoading(campaign.id)"
@click="handleEdit(campaign.id)"
>
<BaseIcon name="edit" class="w-3.5 h-3.5" />
Edit
</button>
<button
type="button"
class="px-2.5 py-1 text-xs font-medium text-blue-700 bg-blue-50 border border-blue-200 rounded-md hover:bg-blue-100 disabled:opacity-50"
class="inline-flex items-center gap-1 px-2.5 py-1.5 text-xs font-medium rounded-md border border-slate-200 text-slate-700 hover:bg-slate-50 transition-colors"
:disabled="isActionLoading(campaign.id)"
@click="handleView(campaign.id)"
>
<BaseIcon name="eye" class="w-3.5 h-3.5" />
View
</button>
</div>
Expand Down Expand Up @@ -333,6 +345,7 @@ import { computed, onMounted, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { campaignClient, fetchAllLists, listMessagesClient, statisticsClient } from '../../api'
import ViewCampaignModal from "./ViewCampaignModal.vue";
import BaseIcon from '../base/BaseIcon.vue'

const pageSize = 5
const route = useRoute()
Expand Down
4 changes: 2 additions & 2 deletions assets/vue/components/dashboard/QuickActionsCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@
import BaseCard from '../../components/base/BaseCard.vue'

const quickActions = [
{ id: 'campaign', label: 'New Campaign', href: '/campaigns' },
{ id: 'campaign', label: 'New Campaign', href: '/campaigns/create' },
{ id: 'subscribers', label: 'Add Subscribers', href: '/subscribers' },
{ id: 'import', label: 'Import List', href: '/subscribers' },
{ id: 'import', label: 'Import List', href: '/lists' },
{ id: 'templates', label: 'Manage Templates', href: '/templates' },
]
</script>
15 changes: 10 additions & 5 deletions assets/vue/components/lists/ListDirectory.vue
Original file line number Diff line number Diff line change
Expand Up @@ -140,41 +140,46 @@
<div class="grid grid-cols-2 gap-2">
<button
type="button"
class="px-2.5 py-2 text-xs font-medium rounded-md border border-red-200 text-red-600 hover:bg-red-50 transition-colors"
class="inline-flex items-center justify-center gap-1 px-2.5 py-1.5 text-xs font-medium rounded-md border border-red-200 text-red-600 hover:bg-red-50 transition-colors"
@click="handleDelete(list)"
>
<BaseIcon name="delete" class="w-3.5 h-3.5" />
Delete
</button>

<button
type="button"
class="px-2.5 py-2 text-xs font-medium rounded-md border border-emerald-200 text-emerald-700 hover:bg-emerald-50 transition-colors"
class="inline-flex items-center justify-center gap-1 px-2.5 py-1.5 text-xs font-medium rounded-md border border-emerald-200 text-emerald-700 hover:bg-emerald-50 transition-colors"
@click="handleAddSubscriber(list)"
>
<BaseIcon name="addUser" class="w-3.5 h-3.5" />
Add Subscriber
</button>

<button
type="button"
class="px-2.5 py-2 text-xs font-medium rounded-md border border-slate-200 text-slate-700 hover:bg-slate-50 transition-colors"
class="inline-flex items-center justify-center gap-1 px-2.5 py-1.5 text-xs font-medium rounded-md border border-slate-200 text-slate-700 hover:bg-slate-50 transition-colors"
@click="handleEdit(list)"
>
<BaseIcon name="edit" class="w-3.5 h-3.5" />
Edit
</button>

<button
type="button"
class="px-2.5 py-2 text-xs font-medium rounded-md border border-blue-200 text-blue-700 hover:bg-blue-50 transition-colors"
class="inline-flex items-center justify-center gap-1 px-2.5 py-1.5 text-xs font-medium rounded-md border border-blue-200 text-blue-700 hover:bg-blue-50 transition-colors"
@click="handleStartCampaign(list)"
>
<BaseIcon name="plane" class="w-3.5 h-3.5" />
Start Campaign
</button>

<button
type="button"
class="col-span-2 px-2.5 py-2 text-xs font-medium rounded-md border border-slate-200 text-slate-700 hover:bg-slate-50 transition-colors"
class="col-span-2 inline-flex items-center justify-center gap-1 px-2.5 py-1.5 text-xs font-medium rounded-md border border-slate-200 text-slate-700 hover:bg-slate-50 transition-colors"
@click="handleViewMembers(list)"
>
<BaseIcon name="eye" class="w-3.5 h-3.5" />
View Members
</button>
</div>
Expand Down
2 changes: 1 addition & 1 deletion assets/vue/components/subscribers/SubscriberDirectory.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div class="bg-white rounded-xl border border-slate-200 shadow-sm overflow-hidden">
<div class="p-4 sm:p-6 border-b border-slate-200 ">
<div class="flex flex-col lg:flex-row justify-between items-start lg:items-center gap-4">
<h2 class="text-xl font-bold text-slate-900 ">Subscriber Directory</h2>
<h2 class="text-xl font-bold text-slate-900 ">Subscribers</h2>
<div class="flex flex-col sm:flex-row gap-2 w-full lg:w-auto">
<div class="flex gap-2 flex-1 sm:w-[400px]">
<div class="relative flex-1">
Expand Down
24 changes: 14 additions & 10 deletions assets/vue/components/subscribers/SubscriberTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<th class="px-6 py-4">Status</th>
<th class="px-6 py-4 text-right">Lists</th>
<th class="px-6 py-4">Created</th>
<th class="px-6 py-4 w-10"></th>
<th class="px-6 py-4 text-right">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-200">
Expand Down Expand Up @@ -48,10 +48,12 @@
</td>
<td class="px-6 py-4 text-right">
<button
class="text-slate-400 hover:text-slate-600"
type="button"
class="inline-flex items-center gap-1 px-2.5 py-1.5 text-xs font-medium rounded-md border border-slate-200 text-slate-700 hover:bg-slate-50 transition-colors"
@click="emit('view', subscriber.id)"
>
<BaseIcon name="eye" class="w-4 h-4" />
<BaseIcon name="eye" class="w-3.5 h-3.5" />
View
</button>
</td>
</tr>
Expand All @@ -69,13 +71,7 @@
<span class="font-medium text-slate-900 truncate max-w-[180px]">
{{ subscriber.email.split('@')[0] }}
</span>
<div class="flex gap-2">
<button
class="text-slate-400 hover:text-slate-600 mr-2"
@click="emit('view', subscriber.id)"
>
<BaseIcon name="eye" class="w-4 h-4" />
</button>
<div class="flex items-center gap-2">
<span
class="px-2.5 py-0.5 rounded-full text-xs font-medium"
:class="subscriber.confirmed ? statusClasses.active : statusClasses.unconfirmed"
Expand All @@ -88,6 +84,14 @@
>
blacklisted
</span>
<button
type="button"
class="inline-flex items-center gap-1 px-2.5 py-1.5 text-xs font-medium rounded-md border border-slate-200 text-slate-700 hover:bg-slate-50 transition-colors"
@click="emit('view', subscriber.id)"
>
<BaseIcon name="eye" class="w-3.5 h-3.5" />
View
</button>
</div>
</div>
<div class="flex flex-col gap-1">
Expand Down
Loading
Loading