From 232c2ac480003c33b80b2d0973f482e01c868f15 Mon Sep 17 00:00:00 2001 From: maruf-pfc Date: Wed, 1 Apr 2026 02:56:42 +0600 Subject: [PATCH 1/2] feat: enhance issues state management, rate limit handling, and add tests - improve useIssues and issues store logic - refine rate limit composable with tests - update date utilities - add unit tests for IssueCard and rate limit - update project configs and README - add GitHub labeler workflow --- .github/labeler.yml | 25 +++++++++++++++++++++ .github/workflows/labeler.yml | 19 ++++++++++++++++ README.md | 4 ++++ bun.lock | 15 ++++++++++++- package.json | 1 + src/composables/useIssues.js | 2 +- src/composables/useRateLimit.js | 2 +- src/stores/issues.js | 2 +- src/utils/date.js | 4 ++-- tests/unit/components/IssueCard.test.js | 2 +- tests/unit/composables/useRateLimit.test.js | 2 +- 11 files changed, 70 insertions(+), 8 deletions(-) create mode 100644 .github/labeler.yml create mode 100644 .github/workflows/labeler.yml diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000..d852531 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,25 @@ +# Add 'frontend' label to any changes in src +frontend: +- src/**/* + +# Add 'tests' label to any changes in tests +tests: +- tests/**/* + +# Add 'ci' label to any changes in .github/workflows +ci: +- .github/workflows/**/* + +# Add 'docker' label to any changes in Dockerfile or docker-compose +docker: +- Dockerfile +- docker-compose.yml +- nginx.conf + +# Add 'docs' label to any changes in README, CONTRIBUTING, etc. +docs: +- README.md +- CONTRIBUTING.md +- CODE_OF_CONDUCT.md +- SECURITY.md +- LICENSE diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 0000000..5427a88 --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,19 @@ +name: "Labeler" +on: +- pull_request_target + +permissions: + contents: read + pull-requests: write + +jobs: + triage: + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/labeler@v5 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" + sync-labels: true diff --git a/README.md b/README.md index f74f13e..95ac7c6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # GitHub Issue Tracker SPA +[![CI](https://github.com/blackstart-labs/github-issue-tracker/actions/workflows/ci.yml/badge.svg)](https://github.com/blackstart-labs/github-issue-tracker/actions/workflows/ci.yml) +[![Deploy](https://github.com/blackstart-labs/github-issue-tracker/actions/workflows/deploy.yml/badge.svg)](https://github.com/blackstart-labs/github-issue-tracker/actions/workflows/deploy.yml) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) + A production-grade, high-performance Single Page Application (SPA) for tracking GitHub issues. Built with **Vue 3**, **Vite**, and **Pinia**, featuring a signature "Editorial Utility" aesthetic—sharp, dense, and intentional. ![GitHub Issue Tracker](./public/demo.webp) diff --git a/bun.lock b/bun.lock index 6f78fb8..a3440af 100644 --- a/bun.lock +++ b/bun.lock @@ -5,7 +5,7 @@ "name": "github-issue-tracker", "dependencies": { "@vueuse/core": "^14.2.1", - "axios": "^1.14.0", + "axios": "1.14.0", "date-fns": "^4.1.0", "dompurify": "^3.3.3", "highlight.js": "^11.11.1", @@ -19,6 +19,7 @@ "@vitejs/plugin-vue": "^6.0.4", "@vitejs/plugin-vue-jsx": "^5.1.4", "@vitest/eslint-plugin": "^1.6.10", + "@vue/eslint-config-prettier": "^10.2.0", "@vue/test-utils": "^2.4.6", "cypress": "^15.11.0", "eslint": "^10.0.3", @@ -267,6 +268,8 @@ "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + "@pkgr/core": ["@pkgr/core@0.2.9", "", {}, "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA=="], + "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="], "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.2", "", {}, "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw=="], @@ -413,6 +416,8 @@ "@vue/devtools-shared": ["@vue/devtools-shared@8.1.1", "", {}, "sha512-+h4ttmJYl/txpxHKaoZcaKpC+pvckgLzIDiSQlaQ7kKthKh8KuwoLW2D8hPJEnqKzXOvu15UHEoGyngAXCz0EQ=="], + "@vue/eslint-config-prettier": ["@vue/eslint-config-prettier@10.2.0", "", { "dependencies": { "eslint-config-prettier": "^10.0.1", "eslint-plugin-prettier": "^5.2.2" }, "peerDependencies": { "eslint": ">= 8.21.0", "prettier": ">= 3.0.0" } }, "sha512-GL3YBLwv/+b86yHcNNfPJxOTtVFJ4Mbc9UU3zR+KVoG7SwGTjPT+32fXamscNumElhcpXW3mT0DgzS9w32S7Bw=="], + "@vue/reactivity": ["@vue/reactivity@3.5.31", "", { "dependencies": { "@vue/shared": "3.5.31" } }, "sha512-DtKXxk9E/KuVvt8VxWu+6Luc9I9ETNcqR1T1oW1gf02nXaZ1kuAx58oVu7uX9XxJR0iJCro6fqBLw9oSBELo5g=="], "@vue/runtime-core": ["@vue/runtime-core@3.5.31", "", { "dependencies": { "@vue/reactivity": "3.5.31", "@vue/shared": "3.5.31" } }, "sha512-AZPmIHXEAyhpkmN7aWlqjSfYynmkWlluDNPHMCZKFHH+lLtxP/30UJmoVhXmbDoP1Ng0jG0fyY2zCj1PnSSA6Q=="], @@ -641,6 +646,8 @@ "eslint-plugin-oxlint": ["eslint-plugin-oxlint@1.51.0", "", { "dependencies": { "jsonc-parser": "^3.3.1" } }, "sha512-lct8LD1AxfHF1PcsuK6mFYals+zX0mx/WP2G4i16h0iR8jpT3xCfGTmTNwXiImcevzGIiJ/VDBgQ7t0B9z2Jeg=="], + "eslint-plugin-prettier": ["eslint-plugin-prettier@5.5.5", "", { "dependencies": { "prettier-linter-helpers": "^1.0.1", "synckit": "^0.11.12" }, "peerDependencies": { "@types/eslint": ">=8.0.0", "eslint": ">=8.0.0", "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", "prettier": ">=3.0.0" }, "optionalPeers": ["@types/eslint", "eslint-config-prettier"] }, "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw=="], + "eslint-plugin-vue": ["eslint-plugin-vue@10.8.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "natural-compare": "^1.4.0", "nth-check": "^2.1.1", "postcss-selector-parser": "^7.1.0", "semver": "^7.6.3", "xml-name-validator": "^4.0.0" }, "peerDependencies": { "@stylistic/eslint-plugin": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "@typescript-eslint/parser": "^7.0.0 || ^8.0.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "vue-eslint-parser": "^10.0.0" }, "optionalPeers": ["@stylistic/eslint-plugin", "@typescript-eslint/parser"] }, "sha512-f1J/tcbnrpgC8suPN5AtdJ5MQjuXbSU9pGRSSYAuF3SHoiYCOdEX6O22pLaRyLHXvDcOe+O5ENgc1owQ587agA=="], "eslint-scope": ["eslint-scope@9.1.2", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="], @@ -679,6 +686,8 @@ "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + "fast-diff": ["fast-diff@1.3.0", "", {}, "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="], + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], @@ -979,6 +988,8 @@ "prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="], + "prettier-linter-helpers": ["prettier-linter-helpers@1.0.1", "", { "dependencies": { "fast-diff": "^1.1.2" } }, "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg=="], + "pretty-bytes": ["pretty-bytes@5.6.0", "", {}, "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg=="], "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="], @@ -1079,6 +1090,8 @@ "symbol-tree": ["symbol-tree@3.2.4", "", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="], + "synckit": ["synckit@0.11.12", "", { "dependencies": { "@pkgr/core": "^0.2.9" } }, "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ=="], + "systeminformation": ["systeminformation@5.31.5", "", { "os": "!aix", "bin": { "systeminformation": "lib/cli.js" } }, "sha512-5SyLdip4/3alxD4Kh+63bUQTJmu7YMfYQTC+koZy7X73HgNqZSD2P4wOZQWtUncvPvcEmnfIjCoygN4MRoEejQ=="], "throttleit": ["throttleit@1.0.1", "", {}, "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ=="], diff --git a/package.json b/package.json index d7c370a..71b1f5f 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@vitejs/plugin-vue": "^6.0.4", "@vitejs/plugin-vue-jsx": "^5.1.4", "@vitest/eslint-plugin": "^1.6.10", + "@vue/eslint-config-prettier": "^10.2.0", "@vue/test-utils": "^2.4.6", "cypress": "^15.11.0", "eslint": "^10.0.3", diff --git a/src/composables/useIssues.js b/src/composables/useIssues.js index 6205abe..7b0ddc3 100644 --- a/src/composables/useIssues.js +++ b/src/composables/useIssues.js @@ -3,7 +3,7 @@ import { useIssuesStore } from '@/stores/issues' import { useRepoStore } from '@/stores/repo' import { useUiStore } from '@/stores/ui' import { githubService, parseLinkHeader } from '@/api/github' -import { useRoute, useRouter } from 'vue-router' +// No router needed here export function useIssues() { const issuesStore = useIssuesStore() diff --git a/src/composables/useRateLimit.js b/src/composables/useRateLimit.js index 579ca08..492bebd 100644 --- a/src/composables/useRateLimit.js +++ b/src/composables/useRateLimit.js @@ -36,7 +36,7 @@ export function useRateLimit() { const response = await githubService.getRateLimit() rateLimitRemaining.value = response.data.rate.remaining rateLimitReset.value = response.data.rate.reset - } catch (e) { + } catch { // Ignore initial load failure silently } } diff --git a/src/stores/issues.js b/src/stores/issues.js index 78f04d3..37fd746 100644 --- a/src/stores/issues.js +++ b/src/stores/issues.js @@ -60,7 +60,7 @@ export const useIssuesStore = defineStore('issues', () => { if (lastPage) { pagination.value.totalPages = parseInt(lastPage, 10) } - } catch (e) { + } catch { // failed to parse } } diff --git a/src/utils/date.js b/src/utils/date.js index a75f080..e4c1967 100644 --- a/src/utils/date.js +++ b/src/utils/date.js @@ -8,7 +8,7 @@ export function formatRelative(date) { if (!date) return '' try { return formatDistanceToNow(new Date(date), { addSuffix: true }) - } catch (e) { + } catch { return '' } } @@ -21,7 +21,7 @@ export function formatAbsolute(date) { if (!date) return '' try { return format(new Date(date), 'MMM d, yyyy') - } catch (e) { + } catch { return '' } } diff --git a/tests/unit/components/IssueCard.test.js b/tests/unit/components/IssueCard.test.js index 599ceb2..74328f9 100644 --- a/tests/unit/components/IssueCard.test.js +++ b/tests/unit/components/IssueCard.test.js @@ -1,4 +1,4 @@ -import { describe, it, expect, vi } from 'vitest' +import { describe, it, expect } from 'vitest' import { mount } from '@vue/test-utils' import IssueCard from '@/components/issue/IssueCard.vue' diff --git a/tests/unit/composables/useRateLimit.test.js b/tests/unit/composables/useRateLimit.test.js index c2767f0..1b9404e 100644 --- a/tests/unit/composables/useRateLimit.test.js +++ b/tests/unit/composables/useRateLimit.test.js @@ -1,4 +1,4 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' +import { describe, it, expect, beforeEach } from 'vitest' import { useRateLimit } from '@/composables/useRateLimit' import { mount } from '@vue/test-utils' import { defineComponent, nextTick } from 'vue' From c18102924d633d4496a9905211874b9d83f3f9c5 Mon Sep 17 00:00:00 2001 From: maruf-pfc Date: Wed, 1 Apr 2026 03:00:56 +0600 Subject: [PATCH 2/2] fix(eslint): resolve flat config error by spreading oxlint config --- eslint.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index c0ed1d9..2778de0 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -14,6 +14,6 @@ export default [ }, ...pluginVue.configs['flat/essential'], - pluginOxlint.configs['flat/recommended'], + ...pluginOxlint.configs['flat/recommended'], skipFormatting, -] +] \ No newline at end of file