-
Notifications
You must be signed in to change notification settings - Fork 6
Dev #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
base: main
Are you sure you want to change the base?
Dev #73
Changes from 10 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 |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| <VirtualHost *:80> | ||
| ServerName frontend.phplist.local | ||
| ServerAdmin webmaster@localhost | ||
|
|
||
| DocumentRoot /{pathToTheProject}/web-frontend/public | ||
|
|
||
| ErrorLog ${APACHE_LOG_DIR}/web-frontend-error.log | ||
| CustomLog ${APACHE_LOG_DIR}/web-frontend-access.log combined | ||
|
|
||
| <Directory /{pathToTheProject}/web-frontend/public> | ||
| Options FollowSymLinks | ||
| AllowOverride All | ||
| Require all granted | ||
| DirectoryIndex app.php | ||
| </Directory> | ||
|
|
||
| <FilesMatch "^\."> | ||
| Require all denied | ||
| </FilesMatch> | ||
|
|
||
| <IfModule proxy_fcgi_module> | ||
| <FilesMatch \.php$> | ||
| SetHandler "proxy:unix:/run/php/php8.1-fpm.sock|fcgi://localhost" | ||
| </FilesMatch> | ||
| </IfModule> | ||
| </VirtualHost> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,13 @@ | ||
| import './styles/app.css'; | ||
| 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(appElement); | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import { createRouter, createWebHistory } from 'vue-router'; | ||
| import DashboardView from '../vue/views/DashboardView.vue' | ||
| import SubscribersView from '../vue/views/SubscribersView.vue' | ||
|
|
||
| export const router = createRouter({ | ||
| history: createWebHistory(), | ||
| routes: [ | ||
| { path: '/', name: 'dashboard', component: DashboardView, meta: { title: 'Dashboard' } }, | ||
| { path: '/subscribers', name: 'subscribers', component: SubscribersView, meta: { title: 'Subscribers' } }, | ||
| { path: '/:pathMatch(.*)*', redirect: '/' }, | ||
| ], | ||
| }); | ||
|
|
||
| router.afterEach((to) => { | ||
| const defaultTitle = 'phpList'; | ||
| const pageTitle = to.meta.title; | ||
| document.title = pageTitle ? `${defaultTitle} - ${pageTitle}` : defaultTitle; | ||
| }); | ||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,16 @@ | ||||||||
| @import "tailwindcss"; | ||||||||
| @tailwind utilities; | ||||||||
|
Comment on lines
+1
to
+2
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🌐 Web query:
💡 Result: To configure Stylelint to ignore Tailwind CSS v4 at-rules like Citations:
🌐 Web query:
💡 Result: Yes, using Citations:
🌐 Web query:
💡 Result: No, using Citations:
Remove In Tailwind CSS v4, 🧹 Proposed fix `@import` "tailwindcss";
-@tailwind utilities;For the Stylelint false positives, the web search confirms you can suppress them by updating your Stylelint config to recognize Tailwind v4's at-rules. Add an 📝 Committable suggestion
Suggested change
🧰 Tools🪛 Stylelint (17.4.0)[error] 2-2: Unexpected unknown at-rule "@tailwind" (scss/at-rule-no-unknown) (scss/at-rule-no-unknown) 🤖 Prompt for AI Agents |
||||||||
|
|
||||||||
| @theme { | ||||||||
| --color-primary: #2563eb; | ||||||||
| --color-secondary: #6b7280; | ||||||||
| --color-success: #16a34a; | ||||||||
| --color-danger: #dc2626; | ||||||||
| --color-info: #0891b2; | ||||||||
| --color-ext-wf1: #543ff6; /** indigo-500 **/ | ||||||||
| --color-ext-wf2: #eef2ff; /** indigo-50 **/ | ||||||||
| --color-ext-wf3: #303F9F; /** indigo-700 **/ | ||||||||
| } | ||||||||
|
|
||||||||
| @source "../../templates/**/*.twig"; | ||||||||
| @source "../**/*.vue"; | ||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,26 +1,13 @@ | ||
| <template> | ||
| <div> | ||
| <h2>Hello from Vue</h2> | ||
| <p>{{ message }}</p> | ||
| <div class="min-h-screen bg-[#F8FAFC] flex text-slate-900 font-sans"> | ||
| <AppSidebar /> | ||
|
|
||
| <div class="flex flex-col flex-1 min-h-screen"> | ||
| <RouterView /> | ||
| </div> | ||
| </div> | ||
| </template> | ||
|
|
||
| <script> | ||
| export default { | ||
| name: 'App', | ||
| data() { | ||
| return { | ||
| message: 'This is a reusable component!' | ||
| } | ||
| }, | ||
| created() { | ||
| console.log('App component created'); | ||
| }, | ||
| mounted() { | ||
| console.log('App component mounted'); | ||
| }, | ||
| updated() { | ||
| console.log('App component updated'); | ||
| } | ||
| } | ||
| <script setup> | ||
| import AppSidebar from './components/sidebar/AppSidebar.vue' | ||
| </script> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import { Client, SubscribersClient } from '@tatevikgr/rest-api-client'; | ||
|
|
||
| const appElement = document.getElementById('vue-app'); | ||
| const apiToken = appElement?.dataset.apiToken; | ||
| const apiBaseUrl = appElement?.dataset.apiBaseUrl; | ||
|
|
||
| if (!apiBaseUrl) { | ||
| console.error('API Base URL is not configured.'); | ||
| } | ||
|
|
||
| const client = new Client(apiBaseUrl || ''); | ||
|
TatevikGr marked this conversation as resolved.
Outdated
|
||
|
|
||
| if (apiToken) { | ||
| client.setSessionId(apiToken); | ||
| } | ||
|
|
||
| export const subscribersClient = new SubscribersClient(client); | ||
| export default client; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| <!-- assets/vue/components/base/BaseBadge.vue --> | ||
| <template> | ||
| <span :class="badgeClass"> | ||
| <slot /> | ||
| </span> | ||
| <!-- Renders a Tailwind badge; styling controlled via variant prop --> | ||
| </template> | ||
|
|
||
| <script setup> | ||
| import { computed } from 'vue' | ||
| const props = defineProps({ | ||
| variant: { | ||
| type: String, | ||
| default: 'neutral', // neutral | counter | ||
| }, | ||
| }) | ||
|
|
||
| const badgeClass = computed(() => { | ||
| const base = 'inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium'; | ||
| switch (props.variant) { | ||
| case 'counter': | ||
| return `${base} bg-indigo-50 text-ext-wf3 border border-indigo-100`; | ||
| case 'neutral': | ||
| default: | ||
| return `${base} bg-gray-100 text-gray-800`; | ||
| } | ||
| }) | ||
| </script> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| <!-- assets/vue/components/base/BaseButton.vue --> | ||
| <template> | ||
| <button | ||
| :class="buttonClass" | ||
| type="button" | ||
| v-bind="$attrs" | ||
| > | ||
| <slot /> | ||
| </button> | ||
| </template> | ||
|
|
||
| <script setup> | ||
| import { computed } from 'vue' | ||
|
|
||
| const props = defineProps({ | ||
| variant: { | ||
| type: String, | ||
| default: 'primary', // primary | secondary | ghost | ||
| }, | ||
| }) | ||
|
|
||
| const buttonClass = computed(() => { | ||
| const base = 'inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2'; | ||
| switch (props.variant) { | ||
| case 'secondary': | ||
| return `${base} text-gray-700 bg-white border-gray-300 hover:bg-gray-50 focus:ring-blue-500`; | ||
| case 'ghost': | ||
| return 'inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-blue-600 bg-transparent hover:bg-blue-50 focus:outline-none'; | ||
| case 'primary': | ||
| default: | ||
| return `${base} text-white bg-blue-600 hover:bg-blue-700 focus:ring-blue-500`; | ||
| } | ||
| }) | ||
| </script> | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| <template> | ||
| <div :class="cardClasses"> | ||
| <div :class="bodyClasses"> | ||
| <slot /> | ||
| </div> | ||
| </div> | ||
|
|
||
| </template> | ||
|
|
||
| <script setup> | ||
| const props = defineProps({ | ||
| variant: { | ||
| type: String, | ||
| default: 'default', // default | subtle | danger | success | ||
| }, | ||
| }) | ||
|
|
||
| const cardVariantMap = { | ||
| default: 'rounded-lg shadow-sm border border-gray-100 bg-white', | ||
| subtle: 'rounded-lg shadow-sm border-0 bg-gray-50', | ||
| danger: 'rounded-lg shadow-sm border-0 bg-red-600 text-white', | ||
| success: 'rounded-lg shadow-sm border-0 bg-green-600 text-white', | ||
| } | ||
|
|
||
| const bodyVariantMap = { | ||
| default: 'p-4', | ||
| subtle: 'p-4', | ||
| danger: 'p-4', | ||
| success: 'p-4', | ||
| } | ||
|
|
||
| const cardClasses = cardVariantMap[props.variant] || cardVariantMap.default | ||
| const bodyClasses = bodyVariantMap[props.variant] || bodyVariantMap.default | ||
| </script> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: phpList/web-frontend
Length of output: 23299
Make the campaign ID visible in the edit page title.
The backend renders
Edit Campaign#123`` for/campaigns/123/edit, but the router's static `meta.title` overwrites it with just `Edit Campaign` after navigation, losing the ID. The Vue component already computes the correct title internally, so the router hook needs to support dynamic meta titles.The proposed fix—making
meta.titlea function that accepts the route and checking for it in therouter.afterEachhook—will solve this.Proposed fix
🤖 Prompt for AI Agents