-
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 all commits
cad7bcf
c8ae45e
1a9c129
21d04aa
722c663
9e0378b
fea446d
718c53a
83ba923
ea253fd
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; | ||
|
|
||
| @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 || ''); | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+3
to
+11
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. Proceeding with empty URL after error log can cause confusing failures. When 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| 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
🌐 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
@themeand@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@tailwindbase;@tailwindcomponents;@tailwindutilities; 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@importtailwindcss💡 Result:
No, using
@tailwindutilities alongside@import"tailwindcss" does not cause duplicates in Tailwind CSS v4. The@tailwinddirectives (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@tailwinddirective 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@tailwindutilities with@import. To fix: - Replace all@tailwindbase;@tailwindcomponents;@tailwindutilities; with@import"tailwindcss"; - Use npx@tailwindcss/upgradefor automated migration. - For runtime generation without duplicates, avoid re-importing full Tailwind; use targeted@sourceor@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@tailwinddirectives from v3 (base, components, utilities) have been completely removed. Having@tailwind utilitieson 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
ignoreAtRulesarray to theat-rule-no-unknownrule (orscss/at-rule-no-unknownfor SCSS) with['theme', 'source', 'apply', 'utility', 'variant', 'plugin', 'config'], or install and extendstylelint-config-tailwindcssif available for your v4 setup.📝 Committable suggestion
🧰 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