diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..c7d122e1 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,46 @@ +name: linter + +on: + push: + branches: + - develop + - main + pull_request: + branches: + - develop + - main + +jobs: + quality: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + + - name: Install Dependencies + run: | + composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist + npm install + + - name: Run Pint + run: vendor/bin/pint + + - name: Frontend Format Check + run: npm run format + + - name: Frontend Lint + run: npm run lint + + - name: Commit changes + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: fix code style + commit_options: '--no-verify' + + # We need to run PHPStan after commiting changes as it does not auto-fix errors. + - name: PHPStan + run: ./vendor/bin/phpstan diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..b80f9224 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,42 @@ +# Dependencies +node_modules/ +vendor/ + +# Build outputs +public/ +bootstrap/ +dist/ +.output/ +.nuxt/ + +# Generated files +*.cache +.env +.env.* +!.env.example + +# Config files +*.config.js +*.config.ts + +# ShadCN Vue components (as per project rules) +resources/js/components/ui/ + +# IDE and Editor files +.idea/ +.vscode/ +*.sublime-project +*.sublime-workspace + +# System files +.DS_Store +Thumbs.db + +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Test coverage +coverage/ diff --git a/app/Http/Controllers/Auth/VerifyEmailController.php b/app/Http/Controllers/Auth/VerifyEmailController.php index 784765e3..2477faa5 100644 --- a/app/Http/Controllers/Auth/VerifyEmailController.php +++ b/app/Http/Controllers/Auth/VerifyEmailController.php @@ -19,7 +19,9 @@ public function __invoke(EmailVerificationRequest $request): RedirectResponse } if ($request->user()->markEmailAsVerified()) { - event(new Verified($request->user())); + /** @var \Illuminate\Contracts\Auth\MustVerifyEmail $user */ + $user = $request->user(); + event(new Verified($user)); } return redirect()->intended(route('dashboard', absolute: false).'?verified=1'); diff --git a/composer.json b/composer.json index 4ad9c9d9..4a06ba39 100644 --- a/composer.json +++ b/composer.json @@ -75,4 +75,4 @@ }, "minimum-stability": "dev", "prefer-stable": true -} \ No newline at end of file +} diff --git a/eslint.config.js b/eslint.config.js index 129735e2..388a5a68 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,68 +1,19 @@ -import js from '@eslint/js'; import prettier from 'eslint-config-prettier'; -import globals from 'globals'; -import typescript from 'typescript-eslint'; import vue from 'eslint-plugin-vue'; -/** @type {import('eslint').Linter.Config[]} */ -export default [ - js.configs.recommended, - ...typescript.configs.recommended, +import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'; + +export default defineConfigWithVueTs( + vue.configs['flat/essential'], + vueTsConfigs.recommended, { - ...vue.configs.flat.recommended, - ...vue.configs["flat/strongly-recommended"], - ...vue.configs["flat/essential"], - languageOptions: { - globals: { - ...globals.browser, - }, - }, - rules: { - "vue/match-component-import-name": "warn", - "vue/match-component-file-name": [ - "error", - { - extensions: ["vue"], - shouldMatchCase: true, - }, - ], - "vue/component-definition-name-casing": ["error", "PascalCase"], - "vue/block-tag-newline": [ - "warn", - { - singleline: "always", - multiline: "always", - maxEmptyLines: 0, - }, - ], - "vue/html-self-closing": [ - "error", - { - html: { - void: "always", - normal: "never", - component: "always", - }, - svg: "always", - math: "always", - }, - ], - "vue/require-default-prop": "off", - }, + ignores: ['vendor', 'node_modules', 'public', 'bootstrap/ssr', 'tailwind.config.js', 'resources/js/components/ui/*'], }, { - plugins: { - "@typescript-eslint": tseslint.plugin, - }, - languageOptions: { - parser: tseslint.parser, - parserOptions: { - project: true, - }, + rules: { + 'vue/multi-word-component-names': 'off', + '@typescript-eslint/no-explicit-any': 'off', }, }, - { - ignores: ['vendor', 'node_modules', 'public', 'bootstrap/ssr', 'tailwind.config.js'], - }, - prettier, // Turn off all rules that might conflict with Prettier -]; \ No newline at end of file + prettier, +); diff --git a/package-lock.json b/package-lock.json index c4bc6151..af6ee464 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,6 +6,7 @@ "": { "dependencies": { "@types/ziggy-js": "^1.3.3", + "@vue/eslint-config-typescript": "^14.3.0", "@vueuse/core": "^12.0.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -1331,7 +1332,6 @@ "version": "8.23.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.23.0.tgz", "integrity": "sha512-vBz65tJgRrA1Q5gWlRfvoH+w943dq9K1p1yDBY2pc+a1nbBLZp7fB9+Hk8DaALUbzjqlMfgaqlVPT1REJdkt/w==", - "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", @@ -1361,7 +1361,6 @@ "version": "8.23.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.23.0.tgz", "integrity": "sha512-h2lUByouOXFAlMec2mILeELUbME5SZRN/7R9Cw2RD2lRQQY08MWMM+PmVVKKJNK1aIwqTo9t/0CvOxwPbRIE2Q==", - "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/scope-manager": "8.23.0", @@ -1386,7 +1385,6 @@ "version": "8.23.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.23.0.tgz", "integrity": "sha512-OGqo7+dXHqI7Hfm+WqkZjKjsiRtFUQHPdGMXzk5mYXhJUedO7e/Y7i8AK3MyLMgZR93TX4bIzYrfyVjLC+0VSw==", - "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/types": "8.23.0", @@ -1404,7 +1402,6 @@ "version": "8.23.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.23.0.tgz", "integrity": "sha512-iIuLdYpQWZKbiH+RkCGc6iu+VwscP5rCtQ1lyQ7TYuKLrcZoeJVpcLiG8DliXVkUxirW/PWlmS+d6yD51L9jvA==", - "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/typescript-estree": "8.23.0", @@ -1428,7 +1425,6 @@ "version": "8.23.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.23.0.tgz", "integrity": "sha512-1sK4ILJbCmZOTt9k4vkoulT6/y5CHJ1qUYxqpF1K/DBAd8+ZUL4LlSCxOssuH5m4rUaaN0uS0HlVPvd45zjduQ==", - "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1442,7 +1438,6 @@ "version": "8.23.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.23.0.tgz", "integrity": "sha512-LcqzfipsB8RTvH8FX24W4UUFk1bl+0yTOf9ZA08XngFwMg4Kj8A+9hwz8Cr/ZS4KwHrmo9PJiLZkOt49vPnuvQ==", - "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/types": "8.23.0", @@ -1469,7 +1464,6 @@ "version": "8.23.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.23.0.tgz", "integrity": "sha512-uB/+PSo6Exu02b5ZEiVtmY6RVYO7YU5xqgzTIVZwTHvvK3HsL8tZZHFaTLFtRG3CsV4A5mhOv+NZx5BlhXPyIA==", - "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", @@ -1493,7 +1487,6 @@ "version": "8.23.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.23.0.tgz", "integrity": "sha512-oWWhcWDLwDfu++BGTZcmXWqpwtkwb5o7fxUIGksMQQDSdPW9prsSnfIOZMlsj4vBOSrcnjIUZMiIjODgGosFhQ==", - "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/types": "8.23.0", @@ -1611,6 +1604,31 @@ "he": "^1.2.0" } }, + "node_modules/@vue/eslint-config-typescript": { + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-14.3.0.tgz", + "integrity": "sha512-bOreIxlSC/xsUdhDdKIHb1grwJah+IokNeJ50LqA1StdOHeSPUxSIPNxyKgRx4YdjhyzC6TKtrCf6yYK99x3Uw==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.20.0", + "fast-glob": "^3.3.3", + "typescript-eslint": "^8.20.0", + "vue-eslint-parser": "^9.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.10.0", + "eslint-plugin-vue": "^9.28.0", + "typescript": ">=4.8.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/@vue/language-core": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.0.tgz", @@ -2838,16 +2856,16 @@ "license": "MIT" }, "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -3130,7 +3148,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, "license": "MIT" }, "node_modules/has-flag": { @@ -5067,7 +5084,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz", "integrity": "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==", - "dev": true, "license": "MIT", "engines": { "node": ">=18.12" @@ -5116,7 +5132,6 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", - "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -5130,7 +5145,6 @@ "version": "8.23.0", "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.23.0.tgz", "integrity": "sha512-/LBRo3HrXr5LxmrdYSOCvoAMm7p2jNizNfbIpCgvG4HMsnoprRUOce/+8VJ9BDYWW68rqIENE/haVLWPeFZBVQ==", - "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/eslint-plugin": "8.23.0", diff --git a/package.json b/package.json index b0721bba..d17b1837 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ }, "dependencies": { "@types/ziggy-js": "^1.3.3", + "@vue/eslint-config-typescript": "^14.3.0", "@vueuse/core": "^12.0.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 00000000..ef26e89f --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,10 @@ +includes: + - vendor/larastan/larastan/extension.neon + +parameters: + + paths: + - app/ + + # Level 9 is the highest level + level: 5 \ No newline at end of file diff --git a/resources/css/app.css b/resources/css/app.css index 3c469006..63fb2f88 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -83,4 +83,4 @@ body { @apply bg-background text-foreground; } -} \ No newline at end of file +} diff --git a/resources/js/app.ts b/resources/js/app.ts index 29a51697..20a52632 100644 --- a/resources/js/app.ts +++ b/resources/js/app.ts @@ -2,8 +2,8 @@ import '../css/app.css'; import { createInertiaApp } from '@inertiajs/vue3'; import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'; -import { createApp, h } from 'vue'; import type { DefineComponent } from 'vue'; +import { createApp, h } from 'vue'; import { ZiggyVue } from '../../vendor/tightenco/ziggy'; import { initializeTheme } from './composables/useAppearance'; diff --git a/resources/js/components/AppContent.vue b/resources/js/components/AppContent.vue index b8d6f71d..96e3d2a9 100644 --- a/resources/js/components/AppContent.vue +++ b/resources/js/components/AppContent.vue @@ -1,25 +1,21 @@ - - - - - - + + + + + + diff --git a/resources/js/components/AppHeader.vue b/resources/js/components/AppHeader.vue index f3682722..09464c8d 100644 --- a/resources/js/components/AppHeader.vue +++ b/resources/js/components/AppHeader.vue @@ -1,56 +1,42 @@ @@ -13,7 +13,7 @@ defineProps() - Laravel - Starter Kit + Laravel + Starter Kit diff --git a/resources/js/components/AppLogoIcon.vue b/resources/js/components/AppLogoIcon.vue index 21b8b3ac..dd011bd4 100644 --- a/resources/js/components/AppLogoIcon.vue +++ b/resources/js/components/AppLogoIcon.vue @@ -6,14 +6,14 @@ defineOptions({ }); interface Props { - class?: HTMLAttributes['class']; + className?: HTMLAttributes['class']; } defineProps(); - + -import { ref, onMounted } from 'vue' -import { SidebarProvider } from '@/components/ui/sidebar' +import { SidebarProvider } from '@/components/ui/sidebar'; +import { onMounted, ref } from 'vue'; -interface Props { - variant?: 'header' | 'sidebar' -} - -const props = withDefaults(defineProps(), { - variant: 'header' -}) - -const isOpen = ref(true) +const isOpen = ref(true); onMounted(() => { - isOpen.value = localStorage.getItem('sidebar') !== 'false' -}) + isOpen.value = localStorage.getItem('sidebar') !== 'false'; +}); const handleSidebarChange = (open: boolean) => { - isOpen.value = open - localStorage.setItem('sidebar', String(open)) -} + isOpen.value = open; + localStorage.setItem('sidebar', String(open)); +}; - - - - - - + + + + + + diff --git a/resources/js/components/AppSidebar.vue b/resources/js/components/AppSidebar.vue index b986583c..a47edd20 100644 --- a/resources/js/components/AppSidebar.vue +++ b/resources/js/components/AppSidebar.vue @@ -2,17 +2,7 @@ import NavFooter from '@/components/NavFooter.vue'; import NavMain from '@/components/NavMain.vue'; import NavUser from '@/components/NavUser.vue'; -import { - Sidebar, - SidebarContent, - SidebarFooter, - SidebarHeader, - SidebarInset, - SidebarMenu, - SidebarMenuButton, - SidebarMenuItem, - SidebarProvider, -} from '@/components/ui/sidebar'; +import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from '@/components/ui/sidebar'; import { type NavItem } from '@/types'; import { BookOpenText, FolderGit2, LayoutDashboard } from 'lucide-vue-next'; import AppLogo from './AppLogo.vue'; @@ -37,7 +27,6 @@ const footerNavItems: NavItem[] = [ icon: BookOpenText, }, ]; - diff --git a/resources/js/components/AppSidebarHeader.vue b/resources/js/components/AppSidebarHeader.vue index c4d79ff7..8f790e2e 100644 --- a/resources/js/components/AppSidebarHeader.vue +++ b/resources/js/components/AppSidebarHeader.vue @@ -1,20 +1,11 @@ diff --git a/resources/js/components/Icon.vue b/resources/js/components/Icon.vue index bd503a10..153f749b 100644 --- a/resources/js/components/Icon.vue +++ b/resources/js/components/Icon.vue @@ -1,35 +1,29 @@ - + diff --git a/resources/js/components/NavMain.vue b/resources/js/components/NavMain.vue index b802578c..5b5a71b4 100644 --- a/resources/js/components/NavMain.vue +++ b/resources/js/components/NavMain.vue @@ -1,12 +1,8 @@ diff --git a/resources/js/components/NavUser.vue b/resources/js/components/NavUser.vue index 7a96cc91..e92bc04e 100644 --- a/resources/js/components/NavUser.vue +++ b/resources/js/components/NavUser.vue @@ -1,21 +1,14 @@ diff --git a/resources/js/components/UserInfo.vue b/resources/js/components/UserInfo.vue index 825971bf..2f27340f 100644 --- a/resources/js/components/UserInfo.vue +++ b/resources/js/components/UserInfo.vue @@ -1,18 +1,18 @@ diff --git a/resources/js/components/UserMenuContent.vue b/resources/js/components/UserMenuContent.vue index c96fdb95..4e9566f7 100644 --- a/resources/js/components/UserMenuContent.vue +++ b/resources/js/components/UserMenuContent.vue @@ -1,20 +1,15 @@ diff --git a/resources/js/layouts/AppLayout.vue b/resources/js/layouts/AppLayout.vue index 94d41459..6df1f712 100644 --- a/resources/js/layouts/AppLayout.vue +++ b/resources/js/layouts/AppLayout.vue @@ -1,18 +1,18 @@ - \ No newline at end of file + diff --git a/resources/js/layouts/AuthLayout.vue b/resources/js/layouts/AuthLayout.vue index 1103544a..02c5e8ec 100644 --- a/resources/js/layouts/AuthLayout.vue +++ b/resources/js/layouts/AuthLayout.vue @@ -1,17 +1,6 @@ diff --git a/resources/js/layouts/app/AppHeaderLayout.vue b/resources/js/layouts/app/AppHeaderLayout.vue index 9caee06f..1c7483fb 100644 --- a/resources/js/layouts/app/AppHeaderLayout.vue +++ b/resources/js/layouts/app/AppHeaderLayout.vue @@ -1,16 +1,16 @@ @@ -20,4 +20,4 @@ const props = withDefaults(defineProps(), { - \ No newline at end of file + diff --git a/resources/js/layouts/app/AppSidebarLayout.vue b/resources/js/layouts/app/AppSidebarLayout.vue index 11c40620..e8079be6 100644 --- a/resources/js/layouts/app/AppSidebarLayout.vue +++ b/resources/js/layouts/app/AppSidebarLayout.vue @@ -1,17 +1,17 @@ @@ -22,4 +22,4 @@ const props = withDefaults(defineProps(), { - \ No newline at end of file + diff --git a/resources/js/layouts/auth/AuthCardLayout.vue b/resources/js/layouts/auth/AuthCardLayout.vue index 726c3af2..e5648b03 100644 --- a/resources/js/layouts/auth/AuthCardLayout.vue +++ b/resources/js/layouts/auth/AuthCardLayout.vue @@ -3,16 +3,6 @@ import AppLogoIcon from '@/components/AppLogoIcon.vue'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Link } from '@inertiajs/vue3'; - -interface Props { - title?: string; - description?: string; -} - -const props = withDefaults(defineProps(), { - title: undefined, - description: undefined, -}); diff --git a/resources/js/layouts/auth/AuthSimpleLayout.vue b/resources/js/layouts/auth/AuthSimpleLayout.vue index 9e9c7fa6..5e42b494 100644 --- a/resources/js/layouts/auth/AuthSimpleLayout.vue +++ b/resources/js/layouts/auth/AuthSimpleLayout.vue @@ -2,16 +2,6 @@ diff --git a/resources/js/layouts/auth/AuthSplitLayout.vue b/resources/js/layouts/auth/AuthSplitLayout.vue index 9286e3f3..80f9753a 100644 --- a/resources/js/layouts/auth/AuthSplitLayout.vue +++ b/resources/js/layouts/auth/AuthSplitLayout.vue @@ -3,16 +3,6 @@ import AppLogoIcon from '@/components/AppLogoIcon.vue'; import { Link, usePage } from '@inertiajs/vue3'; -interface Props { - title?: string; - description?: string; -} - -const props = withDefaults(defineProps(), { - title: undefined, - description: undefined, -}); - const page = usePage(); const name = page.props.name; const quote = page.props.quote; diff --git a/resources/js/layouts/settings/Layout.vue b/resources/js/layouts/settings/Layout.vue index 809b5554..b82ba372 100644 --- a/resources/js/layouts/settings/Layout.vue +++ b/resources/js/layouts/settings/Layout.vue @@ -21,7 +21,6 @@ const sidebarNavItems: NavItem[] = [ ]; const currentPath = window.location.pathname; -const currentItem = sidebarNavItems.find((item) => currentPath === item.href); diff --git a/tsconfig.json b/tsconfig.json index 16d67ac6..b5df0b3e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -119,5 +119,5 @@ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true /* Skip type checking all .d.ts files. */ }, - "include": ["resources/js/**/*.ts", "resources/js/**/*.d.ts", "resources/js/**/*.tsx", "resources/js/**/*.vue"], + "include": ["resources/js/**/*.ts", "resources/js/**/*.d.ts", "resources/js/**/*.tsx", "resources/js/**/*.vue"] }