Skip to content
Open

Dev #73

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
26 changes: 26 additions & 0 deletions apache/web-frontend.conf
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>
8 changes: 6 additions & 2 deletions assets/app.js
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);
}

Binary file added assets/images/avatar.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/logo-48.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions assets/router/index.js
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;
});
16 changes: 16 additions & 0 deletions assets/styles/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@import "tailwindcss";
@tailwind utilities;
Comment on lines +1 to +2
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

How to configure Stylelint to ignore Tailwind CSS v4 at-rules like @themeand@source?

💡 Result:

To configure Stylelint to ignore Tailwind CSS v4 at-rules like @theme and @source, update your Stylelint configuration file (e.g., stylelint.config.js) to override the "at-rule-no-unknown" rule and list the Tailwind v4 at-rules in the ignoreAtRules array. Example configuration: js module.exports = { extends: [ 'stylelint-config-standard', // or your preferred base config ], rules: { 'at-rule-no-unknown': [ true, { ignoreAtRules: [ 'theme', 'source', 'apply', 'utility', 'variant', 'plugin', 'config', 'reference', 'custom-variant' ] } ] } }; If using SCSS, also disable the base rule and configure the SCSS-specific one: js rules: { 'at-rule-no-unknown': null, 'scss/at-rule-no-unknown': [ true, { ignoreAtRules: [ 'theme', 'source', // ... other rules ] } ] } Optionally, install and extend a Tailwind-specific config like stylelint-config-tailwindcss (npm install --save-dev stylelint-config-tailwindcss), though it may need updates for full v4 support: js { extends: [ 'stylelint-config-recommended', 'stylelint-config-tailwindcss' ] } This prevents Stylelint from flagging Tailwind v4's custom at-rules as unknown. Run stylelint on your CSS files to verify.

Citations:


🌐 Web query:

Tailwind CSS v4 @import "tailwindcss" does it include utilities automatically

💡 Result:

Yes, using @import "tailwindcss"; in Tailwind CSS v4 automatically includes all Tailwind utilities (as well as base styles and theme), replacing the old @tailwind base; @tailwind components; @tailwind utilities; directives from v3. You can then use utility classes like text-xl, bg-blue-500, etc. directly in your HTML.

Citations:


🌐 Web query:

Tailwind CSS v4 @tailwindutilities duplicate if used with@import tailwindcss

💡 Result:

No, using @tailwind utilities alongside @import "tailwindcss" does not cause duplicates in Tailwind CSS v4. The @tailwind directives (base, components, utilities) from v3 have been completely removed in v4. The correct and only way to include Tailwind CSS v4 is with a single @import "tailwindcss"; line at the top of your CSS file. Using any @tailwind directive will result in errors or no styles being generated, as confirmed by official docs and upgrade guide. The perceived "duplicate" issue likely stems from misconfigurations during migration or runtime setups where Tailwind is imported/processed multiple times (e.g., build-time CSS + runtime @import "tailwindcss" source(none); without deduplication), leading to regenerated utilities. This is a user setup problem, not inherent to @tailwind utilities with @import. To fix: - Replace all @tailwind base; @tailwind components; @tailwind utilities; with @import "tailwindcss"; - Use npx @tailwindcss/upgrade for automated migration. - For runtime generation without duplicates, avoid re-importing full Tailwind; use targeted @source or @import "tailwindcss/utilities.css" layer(utilities); with source(none) carefully, as shown in GitHub discussions.

Citations:


Remove @tailwind utilities — it's not valid in Tailwind CSS v4.

In Tailwind CSS v4, @import "tailwindcss" is the only directive you need. The @tailwind directives from v3 (base, components, utilities) have been completely removed. Having @tailwind utilities on line 2 will cause errors or result in no styles being applied.

🧹 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 ignoreAtRules array to the at-rule-no-unknown rule (or scss/at-rule-no-unknown for SCSS) with ['theme', 'source', 'apply', 'utility', 'variant', 'plugin', 'config'], or install and extend stylelint-config-tailwindcss if available for your v4 setup.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@import "tailwindcss";
@tailwind utilities;
`@import` "tailwindcss";
🧰 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
Verify each finding against the current code and only fix it if needed.

In `@assets/styles/app.css` around lines 1 - 2, Remove the obsolete Tailwind v3
directive from your stylesheet by deleting the line containing "@tailwind
utilities" and keep only the "@import \"tailwindcss\"" directive in the
top-level stylesheet (locate the import block in assets/styles/app.css or the
file that contains `@import` "tailwindcss" and `@tailwind` utilities). Also update
your Stylelint config to avoid false positives: modify the at-rule-no-unknown
(or scss/at-rule-no-unknown) rule to include an ignoreAtRules array with
['theme','source','apply','utility','variant','plugin','config'] or
alternatively install/extend a Tailwind v4-aware config such as
stylelint-config-tailwindcss so Tailwind v4 at-rules are recognized.


@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";
29 changes: 8 additions & 21 deletions assets/vue/App.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>
18 changes: 18 additions & 0 deletions assets/vue/api.js
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 || '');
Comment on lines +3 to +11
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Proceeding with empty URL after error log can cause confusing failures.

When apiBaseUrl is missing, the code logs an error but then continues with new Client(''). This means API calls will silently fail or hit unexpected endpoints rather than failing fast with a clear message.

Consider throwing or returning early so the app doesn't limp along in a broken state:

🛠️ Suggested fix
 if (!apiBaseUrl) {
     console.error('API Base URL is not configured.');
+    throw new Error('API Base URL is required for the application to function.');
 }

-const client = new Client(apiBaseUrl || '');
+const client = new Client(apiBaseUrl);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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 || '');
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.');
throw new Error('API Base URL is required for the application to function.');
}
const client = new Client(apiBaseUrl);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@assets/vue/api.js` around lines 3 - 11, When apiBaseUrl is missing (derived
from appElement?.dataset.apiBaseUrl), do not proceed to construct the Client
with an empty string; instead fail fast by throwing an Error or returning early
so the app won't continue in a broken state. Update the module initialization
around apiBaseUrl, apiToken, appElement and the creation of new Client(...) to
either throw a descriptive Error (e.g., including that apiBaseUrl is required)
or prevent creating Client when apiBaseUrl is falsy, ensuring any calling code
cannot proceed with an empty base URL.


if (apiToken) {
client.setSessionId(apiToken);
}

export const subscribersClient = new SubscribersClient(client);
export default client;
28 changes: 28 additions & 0 deletions assets/vue/components/base/BaseBadge.vue
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>
35 changes: 35 additions & 0 deletions assets/vue/components/base/BaseButton.vue
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>

34 changes: 34 additions & 0 deletions assets/vue/components/base/BaseCard.vue
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>
Loading
Loading