swadhinbiswas feat: OpenCodeHub v1.1.0 - Enterprise Features & Storage Adapters · 2/15/2026
719a1cb
238 lines · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
---
import { GitBranch, Search, Bell, Plus, Menu } from "lucide-react"
import { GlobalSearch } from "@/components/search/GlobalSearch"

const user = Astro.locals.user;
---

<header class="sticky top-0 z-50 w-full border-b border-white/5 bg-background/60 backdrop-blur-xl supports-[backdrop-filter]:bg-background/40">
  <div class="container h-16 flex items-center justify-between gap-4">
    <!-- Logo -->
    <a href="/" class="flex items-center gap-2 transition-opacity hover:opacity-80">
      <img src="/logo-dark.png" alt="OpenCodeHub" class="h-9 w-auto dark:hidden" />
      <img src="/logo-light.png" alt="OpenCodeHub" class="h-9 w-auto hidden dark:block" />
    </a>

    <!-- Navigation -->
    <nav class="hidden md:flex items-center gap-1 text-sm font-medium">
      <a href="/explore" class="px-3 py-2 rounded-md text-muted-foreground transition-all hover:text-foreground hover:bg-muted/50">
        Explore
      </a>
      <a href="/issues" class="px-3 py-2 rounded-md text-muted-foreground transition-all hover:text-foreground hover:bg-muted/50">
        Issues
      </a>
      <a href="/pulls" class="px-3 py-2 rounded-md text-muted-foreground transition-all hover:text-foreground hover:bg-muted/50">
        Pulls
      </a>
      <a href="/actions" class="px-3 py-2 rounded-md text-muted-foreground transition-all hover:text-foreground hover:bg-muted/50">
        Actions
      </a>
      <a href="/cli" class="px-3 py-2 rounded-md text-muted-foreground transition-all hover:text-foreground hover:bg-muted/50">
        CLI
      </a>
      <a href="/docs/" class="px-3 py-2 rounded-md text-muted-foreground transition-all hover:text-foreground hover:bg-muted/50">
        Docs
      </a>
      <div class="mx-2 h-4 w-px bg-border/50"></div>
      <a href="/admin" class="flex items-center gap-2 px-3 py-2 rounded-md text-purple-400/90 transition-all hover:text-purple-400 hover:bg-purple-500/10">
        <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-activity"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>
        <span>Mission Control</span>
      </a>
    </nav>

    <!-- Search -->
    <div class="hidden md:flex flex-1 max-w-sm">
      <div class="relative w-full group">
        <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors" />
        <input
          type="search"
          placeholder="Search..."
          class="w-full h-9 rounded-full border border-border/50 bg-secondary/30 px-9 text-sm text-foreground placeholder:text-muted-foreground/70 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary/50 transition-all"
        />
        <kbd class="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 hidden h-5 select-none items-center gap-1 rounded bg-background/50 px-1.5 font-mono text-[10px] font-medium opacity-50 sm:flex border border-border/50">
          <span class="text-xs">⌘</span>K
        </kbd>
      </div>
    </div>

    <!-- Actions -->
    <div class="flex items-center gap-3">
      {user ? (
        <>
          <!-- Create New -->
          <div class="relative" id="create-menu">
            <button
              class="flex h-9 w-9 items-center justify-center rounded-full border border-border/50 bg-background transition-all hover:bg-accent hover:text-accent-foreground focus:outline-none focus:ring-2 focus:ring-primary/20"
              aria-label="Create new"
            >
              <Plus className="h-4 w-4" />
            </button>
            <div id="create-dropdown" class="hidden absolute right-0 mt-2 w-48 rounded-lg border bg-popover p-1 shadow-lg animate-in fade-in zoom-in-95 duration-200">
              <a href="/new" class="flex items-center rounded-md px-2 py-2 text-sm hover:bg-accent transition-colors">
                New Repository
              </a>
              <a href="/new/import" class="flex items-center rounded-md px-2 py-2 text-sm hover:bg-accent transition-colors">
                Import Repository
              </a>
            </div>
          </div>

          <!-- Notifications -->
          <a
            href="/notifications"
            class="flex h-9 w-9 items-center justify-center rounded-full border border-border/50 bg-background transition-all hover:bg-accent hover:text-accent-foreground focus:outline-none focus:ring-2 focus:ring-primary/20 relative"
            aria-label="Notifications"
          >
            <Bell className="h-4 w-4" />
            <span class="absolute -top-0.5 -right-0.5 flex h-3 w-3 items-center justify-center rounded-full bg-red-500">
              <span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75"></span>
              <span class="relative inline-flex rounded-full h-2 w-2 bg-red-500"></span>
            </span>
          </a>

          <!-- User Menu -->
          <div class="relative" id="user-menu">
            <button
              class="flex h-9 w-9 items-center justify-center rounded-full bg-muted border border-border/50 ring-offset-2 hover:ring-2 hover:ring-primary/20 overflow-hidden transition-all"
              aria-label="User menu"
            >
              {user.avatarUrl ? (
                <img src={user.avatarUrl} alt={user.username} class="h-full w-full object-cover" />
              ) : (
                <span class="text-sm font-medium">{user.username.charAt(0).toUpperCase()}</span>
              )}
            </button>
            <div id="user-dropdown" class="hidden absolute right-0 mt-2 w-64 rounded-xl border bg-popover p-2 shadow-xl animate-in fade-in zoom-in-95 duration-200">
              <div class="px-3 py-2.5">
                <p class="font-semibold text-sm text-foreground">{user.displayName || user.username}</p>
                <p class="text-xs text-muted-foreground truncate">@{user.username}</p>
              </div>
              <div class="h-px bg-border/50 my-1"></div>
              <a href="/profile" class="flex items-center rounded-lg px-3 py-2 text-sm hover:bg-accent transition-colors">
                Your Profile
              </a>
              <a href="/repositories" class="flex items-center rounded-lg px-3 py-2 text-sm hover:bg-accent transition-colors">
                Your Repositories
              </a>
              <a href="/dashboard" class="flex items-center rounded-lg px-3 py-2 text-sm hover:bg-accent transition-colors">
                Dashboard
              </a>
              <a href="/stars" class="flex items-center rounded-lg px-3 py-2 text-sm hover:bg-accent transition-colors">
                Your Stars
              </a>
              <div class="h-px bg-border/50 my-1"></div>
              <a href="/settings" class="flex items-center rounded-lg px-3 py-2 text-sm hover:bg-accent transition-colors">
                Settings
              </a>
              <div class="h-px bg-border/50 my-1"></div>
              <form action="/api/auth/logout" method="POST">
                <button type="submit" class="w-full flex items-center rounded-lg px-3 py-2 text-sm hover:bg-red-500/10 text-red-500 transition-colors">
                  Sign Out
                </button>
              </form>
            </div>
          </div>
        </>
      ) : (
        <div class="flex items-center gap-2">
          <a href="/login" class="inline-flex items-center justify-center rounded-full text-sm font-medium h-9 px-5 hover:bg-muted transition-colors">
            Sign in
          </a>
          <a href="/register" class="inline-flex items-center justify-center rounded-full text-sm font-medium h-9 px-5 bg-primary text-primary-foreground shadow-lg shadow-primary/20 hover:bg-primary/90 hover:scale-105 active:scale-95 transition-all duration-300">
            Sign up
          </a>
        </div>
      )}

      <!-- Mobile Menu -->
      <button
        class="flex h-9 w-9 items-center justify-center rounded-full border border-border/50 bg-background transition-all hover:bg-accent hover:text-accent-foreground focus:outline-none focus:ring-2 focus:ring-primary/20 md:hidden"
        aria-label="Menu"
        id="mobile-menu-btn"
      >
        <Menu className="h-4 w-4" />
      </button>
    </div>
  </div>

  <!-- Mobile Navigation -->
  <div id="mobile-nav" class="hidden border-t md:hidden bg-background/95 backdrop-blur-xl">
    <nav class="container py-4 space-y-2">
      <a href="/explore" class="block px-3 py-2 text-sm font-medium hover:bg-accent rounded-lg transition-colors">Explore</a>
      <a href="/issues" class="block px-3 py-2 text-sm font-medium hover:bg-accent rounded-lg transition-colors">Issues</a>
      <a href="/pulls" class="block px-3 py-2 text-sm font-medium hover:bg-accent rounded-lg transition-colors">Pull Requests</a>
      <a href="/actions" class="block px-3 py-2 text-sm font-medium hover:bg-accent rounded-lg transition-colors">Actions</a>
      <a href="/cli" class="block px-3 py-2 text-sm font-medium hover:bg-accent rounded-lg transition-colors">CLI</a>
      <a href="/admin" class="block px-3 py-2 text-sm font-medium text-purple-400 hover:bg-purple-500/10 rounded-lg transition-colors">Mission Control</a>

      <div class="pt-2">
        <button
          class="w-full flex items-center h-10 rounded-lg border border-input bg-background/50 px-3 text-sm text-muted-foreground text-left"
          id="mobile-search-trigger"
        >
          <Search className="mr-2 h-4 w-4" />
          Search...
        </button>
      </div>
    </nav>
  </div>

  <GlobalSearch client:idle />
</header>

<script>
  // Dropdown toggle functionality
  function setupDropdown(triggerId: string, dropdownId: string) {
    const trigger = document.getElementById(triggerId)
    const dropdown = document.getElementById(dropdownId)

    if (trigger && dropdown) {
      trigger.addEventListener('click', (e) => {
        e.stopPropagation()
        dropdown.classList.toggle('hidden')
      })
    }
  }

  setupDropdown('create-menu', 'create-dropdown')
  setupDropdown('user-menu', 'user-dropdown')

  // Mobile menu toggle
  const mobileBtn = document.getElementById('mobile-menu-btn')
  const mobileNav = document.getElementById('mobile-nav')

  if (mobileBtn && mobileNav) {
    mobileBtn.addEventListener('click', () => {
      mobileNav.classList.toggle('hidden')
    })
  }

  // Close dropdowns when clicking outside
  document.addEventListener('click', () => {
    document.querySelectorAll('[id$="-dropdown"]').forEach(el => {
      el.classList.add('hidden')
    })
  })

  // Search Triggers
  const searchInput = document.querySelector('input[type="search"]') as HTMLInputElement
  const mobileSearchTrigger = document.getElementById('mobile-search-trigger')

  function openSearch() {
      window.dispatchEvent(new CustomEvent('open-global-search'));
  }

  if (searchInput) {
      searchInput.addEventListener('click', (e) => {
          e.preventDefault();
          searchInput.blur(); // Don't actually focus
          openSearch();
      });
      // Also handle keydown k if focused (though usually we intercept global k)
  }

  if (mobileSearchTrigger) {
      mobileSearchTrigger.addEventListener('click', openSearch);
  }
</script>