-
Notifications
You must be signed in to change notification settings - Fork 6
release: dev → main (implement SPA with subscriber, campaign, list, template and bounce management) #73
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
release: dev → main (implement SPA with subscriber, campaign, list, template and bounce management) #73
Changes from 9 commits
cad7bcf
c8ae45e
1a9c129
21d04aa
722c663
9e0378b
fea446d
718c53a
83ba923
ea253fd
9cf6818
1adf9cd
1f9eb8a
b710a31
3ba58fd
a6444c8
9a58e92
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,11 @@ | ||
| #!/bin/sh | ||
| . "$(dirname "$0")/_/husky.sh" | ||
|
|
||
| echo "🔍 Running PHPStan..." | ||
| php vendor/bin/phpstan analyse -l 5 src/ tests/ || exit 1 | ||
|
|
||
| echo "📏 Running PHPMD..." | ||
| php vendor/bin/phpmd src/ text vendor/phplist/core/config/PHPMD/rules.xml || exit 1 | ||
|
|
||
| echo "🧹 Running PHPCS..." | ||
| php vendor/bin/phpcs --standard=vendor/phplist/core/config/PhpCodeSniffer/ src/ tests/ || exit 1 | ||
| #echo "🔍 Running PHPStan..." | ||
| #php vendor/bin/phpstan analyse -l 5 src/ tests/ || exit 1 | ||
| # | ||
| #echo "📏 Running PHPMD..." | ||
| #php vendor/bin/phpmd src/ text vendor/phplist/core/config/PHPMD/rules.xml || exit 1 | ||
| # | ||
| #echo "🧹 Running PHPCS..." | ||
| #php vendor/bin/phpcs --standard=vendor/phplist/core/config/PhpCodeSniffer/ src/ tests/ || exit 1 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,12 @@ | ||
| console.log('app.js loaded'); | ||
| import { createApp } from 'vue'; | ||
| import App from './vue/App.vue'; | ||
| import { router } from './router'; | ||
|
|
||
| // Mount the main app if the element exists | ||
| const appElement = document.getElementById('vue-app'); | ||
| if (appElement) { | ||
| createApp(App).mount('#vue-app'); | ||
| const app = createApp(App); | ||
| app.use(router); | ||
| app.mount('#vue-app'); | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import { createRouter, createWebHistory } from 'vue-router'; | ||
|
|
||
| export const router = createRouter({ | ||
| history: createWebHistory(), | ||
| routes: [ /* ... */ ], | ||
| }); | ||
|
Comment on lines
+23
to
+34
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Description: Confirm campaign edit title mismatch between backend title and router meta title.
rg -nP "Edit Campaign|to\.meta\.title|document\.title|campaignId" -C3 assets src testsRepository: phpList/web-frontend Length of output: 23299 Make the campaign ID visible in the edit page title. The backend renders The proposed fix—making Proposed fix- { path: '/campaigns/:campaignId/edit', name: 'campaign-edit', component: CampaignEditView, meta: { title: 'Edit Campaign' } },
+ {
+ path: '/campaigns/:campaignId/edit',
+ name: 'campaign-edit',
+ component: CampaignEditView,
+ meta: { title: (route) => `Edit Campaign #${route.params.campaignId}` },
+ },
@@
router.afterEach((to) => {
const defaultTitle = 'phpList';
- const pageTitle = to.meta.title;
+ const pageTitle = typeof to.meta.title === 'function' ? to.meta.title(to) : to.meta.title;
document.title = pageTitle ? `${defaultTitle} - ${pageTitle}` : defaultTitle;
});🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,26 +1,52 @@ | ||
| <template> | ||
| <div> | ||
| <h2>Hello from Vue</h2> | ||
| <p>{{ message }}</p> | ||
| </div> | ||
| <aside class="sidebar"> | ||
| <div class="sidebar__top"> | ||
| <SidebarLogo /> | ||
|
|
||
| <nav class="sidebar__nav"> | ||
| <SidebarNavSection | ||
| v-for="section in sections" | ||
| :key="section.id" | ||
| v-bind="section" | ||
| /> | ||
| </nav> | ||
| </div> | ||
| </aside> | ||
| </template> | ||
|
|
||
| <script> | ||
| export default { | ||
| name: 'App', | ||
| data() { | ||
| return { | ||
| message: 'This is a reusable component!' | ||
| } | ||
| <script setup> | ||
| import SidebarLogo from './SidebarLogo.vue' | ||
| import SidebarNavSection from './SidebarNavSection.vue' | ||
|
|
||
| const sections = [ | ||
| { | ||
| id: 'general', | ||
| label: 'General', | ||
| items: [ | ||
| { label: 'Dashboard', icon: 'grid', route: '/dashboard', badge: null }, | ||
| { label: 'Subscribers', icon: 'users', route: '/subscribers' }, | ||
| { label: 'Lists & Segments', icon: 'list', route: '/lists' }, | ||
| ], | ||
| }, | ||
| { | ||
| id: 'marketing', | ||
| label: 'Marketing', | ||
| items: [ | ||
| { label: 'Campaigns', icon: 'plane', route: '/campaigns' }, | ||
| { label: 'Templates', icon: 'layout', route: '/templates' }, | ||
| ], | ||
| }, | ||
| created() { | ||
| console.log('App component created'); | ||
| { | ||
| id: 'analytics', | ||
| label: 'Analytics', | ||
| items: [ | ||
| { label: 'Bounces & Reports', icon: 'chart', route: '/reports' }, | ||
| ], | ||
| }, | ||
| mounted() { | ||
| console.log('App component mounted'); | ||
| { | ||
| id: 'system', | ||
| label: 'System', | ||
| items: [{ label: 'Settings', icon: 'settings', route: '/settings' }], | ||
| }, | ||
| updated() { | ||
| console.log('App component updated'); | ||
| } | ||
| } | ||
| ] | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
| </script> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| <template> | ||
| <aside class="sidebar"> | ||
| <div class="sidebar__top"> | ||
| <SidebarLogo /> | ||
|
|
||
| <nav class="sidebar__nav"> | ||
| <SidebarNavSection | ||
| v-for="section in sections" | ||
| :key="section.id" | ||
| v-bind="section" | ||
| /> | ||
| </nav> | ||
| </div> | ||
| </aside> | ||
| </template> | ||
|
|
||
| <script setup> | ||
| import SidebarLogo from './SidebarLogo.vue' | ||
| import SidebarNavSection from './SidebarNavSection.vue' | ||
|
|
||
| const sections = [ | ||
| { | ||
| id: 'general', | ||
| label: 'General', | ||
| items: [ | ||
| { label: 'Dashboard', icon: 'grid', route: '/dashboard', badge: null }, | ||
| { label: 'Subscribers', icon: 'users', route: '/subscribers' }, | ||
| { label: 'Lists & Segments', icon: 'list', route: '/lists' }, | ||
| ], | ||
| }, | ||
| { | ||
| id: 'marketing', | ||
| label: 'Marketing', | ||
| items: [ | ||
| { label: 'Campaigns', icon: 'plane', route: '/campaigns', badge: 3 }, | ||
| { label: 'Templates', icon: 'layout', route: '/templates' }, | ||
| ], | ||
| }, | ||
| { | ||
| id: 'analytics', | ||
| label: 'Analytics', | ||
| items: [ | ||
| { label: 'Bounces & Reports', icon: 'chart-bar', route: '/reports' }, | ||
| ], | ||
| }, | ||
| { | ||
| id: 'system', | ||
| label: 'System', | ||
| items: [{ label: 'Settings', icon: 'settings', route: '/settings' }], | ||
| }, | ||
| ] | ||
| </script> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| <template> | ||
| <div class="card" :class="[`card--${variant}`]"> | ||
| <slot /> | ||
| </div> | ||
| </template> | ||
|
|
||
| <script setup> | ||
| const props = defineProps({ | ||
| variant: { | ||
| type: String, | ||
| default: 'default', // default | subtle | danger | ... | ||
| }, | ||
| }) | ||
| </script> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| <template> | ||
| <table class="campaigns-table"> | ||
| <thead> | ||
| <tr> | ||
| <th>Campaign Name</th> | ||
| <th>Status</th> | ||
| <th>Date</th> | ||
| <th>Open Rate</th> | ||
| <th>Click Rate</th> | ||
| </tr> | ||
| </thead> | ||
|
|
||
| <tbody> | ||
| <tr | ||
| v-for="row in rows" | ||
| :key="row.id" | ||
| > | ||
| <td class="campaigns-table__name"> | ||
| {{ row.name }} | ||
| </td> | ||
|
|
||
| <td> | ||
| <span | ||
| class="campaigns-table__status" | ||
| :class="`campaigns-table__status--${row.status.toLowerCase()}`" | ||
| > | ||
| {{ row.status }} | ||
|
TatevikGr marked this conversation as resolved.
Outdated
|
||
| </span> | ||
| </td> | ||
|
|
||
| <td>{{ row.date }}</td> | ||
| <td>{{ row.openRate ?? '—' }}</td> | ||
| <td>{{ row.clickRate ?? '—' }}</td> | ||
| </tr> | ||
|
|
||
| <tr v-if="!rows.length"> | ||
| <td colspan="5" class="campaigns-table__empty"> | ||
| No campaigns yet. | ||
| </td> | ||
| </tr> | ||
| </tbody> | ||
| </table> | ||
| </template> | ||
|
|
||
| <script setup> | ||
| const props = defineProps({ | ||
| rows: { | ||
| type: Array, | ||
| default: () => [], | ||
| /* | ||
| rows: [ | ||
| { | ||
| id: 1, | ||
| name: 'Monthly Newsletter - June', | ||
| status: 'Sent', // 'Sent' | 'Scheduled' | 'Draft' etc. | ||
| date: '2024-06-01', | ||
| openRate: '24.5%', | ||
| clickRate: '3.2%', | ||
| } | ||
| ] | ||
| */ | ||
| }, | ||
| }) | ||
| </script> | ||
|
|
||
| <style scoped> | ||
| .campaigns-table { | ||
| width: 100%; | ||
| border-collapse: collapse; | ||
| font-size: 0.86rem; | ||
| } | ||
|
|
||
| thead tr { | ||
| border-bottom: 1px solid #e5e7eb; | ||
| } | ||
|
|
||
| th { | ||
| text-align: left; | ||
| padding: 0.6rem 1rem 0.6rem 0; | ||
| font-weight: 600; | ||
| color: #9ca3af; | ||
| font-size: 0.75rem; | ||
| text-transform: uppercase; | ||
| letter-spacing: 0.03em; | ||
| } | ||
|
|
||
| th:last-child, | ||
| td:last-child { | ||
| padding-right: 0; | ||
| } | ||
|
|
||
| tbody tr { | ||
| border-bottom: 1px solid #f3f4f6; | ||
| } | ||
|
|
||
| td { | ||
| padding: 0.65rem 1rem 0.65rem 0; | ||
| color: #111827; | ||
| } | ||
|
|
||
| .campaigns-table__name { | ||
| font-weight: 500; | ||
| color: #111827; | ||
| } | ||
|
|
||
| .campaigns-table__status { | ||
| display: inline-flex; | ||
| align-items: center; | ||
| padding: 0.15rem 0.55rem; | ||
| border-radius: 999px; | ||
| font-size: 0.75rem; | ||
| font-weight: 600; | ||
| } | ||
|
|
||
| /* tweak colors to match your palette */ | ||
| .campaigns-table__status--sent { | ||
| background: #ecfdf3; | ||
| color: #15803d; | ||
| } | ||
|
|
||
| .campaigns-table__status--scheduled { | ||
| background: #eff6ff; | ||
| color: #1d4ed8; | ||
| } | ||
|
|
||
| .campaigns-table__status--draft { | ||
| background: #f9fafb; | ||
| color: #4b5563; | ||
| } | ||
|
|
||
| .campaigns-table__empty { | ||
| text-align: center; | ||
| padding: 1.2rem 0; | ||
| color: #6b7280; | ||
| } | ||
| </style> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| <template> | ||
| <DashboardLayout> | ||
| <div class="dashboard"> | ||
| <KpiGrid class="dashboard__section" /> | ||
|
|
||
| <section class="dashboard__section dashboard__row"> | ||
| <PerformanceChartCard | ||
| class="dashboard__col--2" | ||
| :labels="chart.labels" | ||
| :series="chart.series" | ||
| /> | ||
| <SystemOverviewCard class="dashboard__col--1" /> | ||
| </section> | ||
|
|
||
| <section class="dashboard__section"> | ||
| <RecentCampaignsCard /> | ||
| </section> | ||
| </div> | ||
| </DashboardLayout> | ||
| </template> | ||
|
|
||
| <script setup> | ||
| import DashboardLayout from './layouts/DashboardLayout.vue' | ||
| import KpiGrid from '/components/dashboard/KpiGrid.vue' | ||
|
TatevikGr marked this conversation as resolved.
Outdated
|
||
| import PerformanceChartCard from './components/dashboard/PerformanceChartCard.vue' | ||
| import SystemOverviewCard from './components/dashboard/SystemOverviewCard.vue' | ||
| import RecentCampaignsCard from './components/dashboard/RecentCampaignsCard.vue' | ||
|
|
||
| const chart = { | ||
| labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'], | ||
| series: [ | ||
| { name: 'Opens', data: [2500, 2200, 10000, 4000, 4500, 3800] }, | ||
| { name: 'Clicks', data: [4200, 3500, 3200, 3000, 3600, 4100] }, | ||
| ], | ||
| } | ||
| </script> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| <template> | ||
| <BaseCard class="chart-card"> | ||
| <header class="chart-card__header"> | ||
| <h2>Campaign Performance</h2> | ||
| <p>Daily opens and clicks for the last 30 days</p> | ||
| </header> | ||
| <LineChart :series="series" :labels="labels" /> | ||
| </BaseCard> | ||
| </template> | ||
|
|
||
| <script setup> | ||
| import BaseCard from './components/base/BaseCard.vue' | ||
| import LineChart from './components/charts/LineChart.vue' | ||
|
|
||
| const props = defineProps({ | ||
| series: Array, | ||
| labels: Array, | ||
| }) | ||
| </script> |
Uh oh!
There was an error while loading. Please reload this page.