Skip to content
Merged
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
34 changes: 0 additions & 34 deletions .github/workflows/deploy.yml

This file was deleted.

11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
# 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)

> [!TIP]
> If you find this project useful, please consider giving it a **Star** on GitHub! It helps more developers discover this tool.

## ✨ Features

- **Modern UI/UX**: "Editorial Utility" design system with vanilla CSS and custom properties.
Expand Down Expand Up @@ -88,6 +90,13 @@ docker-compose up -d

The application will be accessible at `http://localhost:8080`.

## 🚀 Deployment

### Vercel (Recommended)
This project is optimized for deployment on Vercel. Connect your repository to Vercel and it will automatically detect the Vite environment. The provided [vercel.json](vercel.json) ensures correct SPA routing.

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fblackstart-labs%2Fgithub-issue-tracker)

## 🧪 Testing

Run unit tests with Vitest:
Expand Down
8 changes: 8 additions & 0 deletions src/assets/styles/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,11 @@ body {
/* instant swap for dark mode toggle */
transition: none;
}

/* Global Layout Utilities */
.container {
width: 100%;
max-width: 1400px;
margin: 0 auto;
padding: 0 var(--space-24);
}
129 changes: 92 additions & 37 deletions src/components/layout/AppHeader.vue
Original file line number Diff line number Diff line change
@@ -1,38 +1,51 @@
<template>
<header class="app-header">
<div class="app-header-left">
<router-link to="/" class="app-logo">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
<path fill-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clip-rule="evenodd" />
</svg>
<span class="app-logo-text">Issues</span>
</router-link>
</div>

<div class="app-header-right">
<div v-if="rateLimitRemaining !== null" class="app-rate-limit">
<span class="rate-limit-count" :class="{ 'rate-limit-warning': rateLimitRemaining < 100 }">
{{ rateLimitRemaining }} rem
</span>
<div class="container header-inner">
<div class="app-header-left">
<router-link to="/" class="app-logo">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
<path fill-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clip-rule="evenodd" />
</svg>
<span class="app-logo-text">Issues</span>
</router-link>
</div>

<button class="icon-button" @click="toggleTheme" aria-label="Toggle Theme">
<svg v-if="mode === 'dark'" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="5"/>
<path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/>
</svg>
<svg v-else width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
</svg>
</button>

<button class="icon-button" @click="uiStore.toggleSettingsPanel()" aria-label="Settings">
<img v-if="currentUser" :src="currentUser.avatar_url" class="user-avatar" alt="Avatar"/>
<svg v-else width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
<circle cx="12" cy="7" r="4"/>
</svg>
</button>
<div class="app-header-right">
<!-- Star on GitHub Button -->
<a href="https://github.com/blackstart-labs/github-issue-tracker" target="_blank" class="star-button" title="Star on GitHub">
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/>
</svg>
<span>Star</span>
</a>

<div v-if="rateLimitRemaining !== null" class="app-rate-limit" title="API Requests Remaining">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>
</svg>
<span class="rate-limit-count" :class="{ 'rate-limit-warning': rateLimitRemaining < 100 }">
{{ rateLimitRemaining }}
</span>
</div>

<button class="icon-button" @click="toggleTheme" aria-label="Toggle Theme">
<svg v-if="mode === 'dark'" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="5"/>
<path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/>
</svg>
<svg v-else width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
</svg>
</button>

<button class="icon-button" @click="uiStore.toggleSettingsPanel()" aria-label="Settings">
<img v-if="currentUser" :src="currentUser.avatar_url" class="user-avatar" alt="Avatar"/>
<svg v-else width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
<circle cx="12" cy="7" r="4"/>
</svg>
</button>
</div>
</div>

<!-- Settings Panel (Slide-in) -->
Expand Down Expand Up @@ -117,21 +130,25 @@ onMounted(() => {

<style scoped>
.app-header {
display: flex;
align-items: center;
justify-content: space-between;
height: 60px;
padding: 0 var(--space-24);
background-color: var(--color-surface);
border-bottom: 1px solid var(--color-border);
position: relative;
position: sticky;
top: 0;
z-index: 100;
}

.header-inner {
display: flex;
align-items: center;
justify-content: space-between;
height: 100%;
}

.app-header-left, .app-header-right {
display: flex;
align-items: center;
gap: var(--space-16);
gap: var(--space-12);
}

.app-logo {
Expand All @@ -140,6 +157,10 @@ onMounted(() => {
gap: var(--space-8);
color: var(--color-text-primary);
text-decoration: none;
transition: opacity var(--transition-fast);
}
.app-logo:hover {
opacity: 0.8;
}

.app-logo-text {
Expand All @@ -149,6 +170,31 @@ onMounted(() => {
letter-spacing: -0.02em;
}

/* Star Button */
.star-button {
display: flex;
align-items: center;
gap: var(--space-6);
padding: var(--space-4) var(--space-12);
height: 28px;
font-size: var(--text-xs);
font-weight: 500;
color: var(--color-text-primary);
background-color: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-input);
text-decoration: none;
transition: all var(--transition-fast);
}
.star-button:hover {
background-color: var(--color-surface-subtle);
border-color: var(--color-border-strong);
text-decoration: none;
}
.star-button svg {
color: #eab308; /* Star yellow */
}

.icon-button {
display: flex;
align-items: center;
Expand All @@ -171,9 +217,18 @@ onMounted(() => {
}

.app-rate-limit {
display: flex;
align-items: center;
gap: var(--space-4);
font-family: var(--font-mono);
font-size: var(--text-xs);
color: var(--color-text-secondary);
padding: var(--space-4) var(--space-8);
background-color: var(--color-surface-subtle);
border-radius: var(--radius-input);
}
.rate-limit-count {
line-height: 1;
}
.rate-limit-warning {
color: var(--color-danger);
Expand Down
9 changes: 9 additions & 0 deletions vercel.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"rewrites": [
{
"source": "/(.*)",
"destination": "/index.html"
}
],
"cleanUrls": true
}
3 changes: 1 addition & 2 deletions vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import vueDevTools from 'vite-plugin-vue-devtools'

// https://vite.dev/config/
export default defineConfig({
base: './', // Using relative base for robust deployment on GitHub Pages (Hash Mode)
base: '/',
plugins: [
vue(),
vueJsx(),
Expand Down
Loading