| 1 | <% content_for :title, "Dashboard" %> |
| 2 | |
| 3 | <div class="mb-6"> |
| 4 | <h1 class="text-xl font-semibold text-gray-100">Overview</h1> |
| 5 | <p class="text-sm text-gray-500">The business at a glance — growth, catalog, and recurring revenue.</p> |
| 6 | </div> |
| 7 | |
| 8 | <%# Growth %> |
| 9 | <h2 class="text-xs font-semibold uppercase tracking-wide text-gray-500 mb-2">Growth</h2> |
| 10 | <div class="grid grid-cols-2 lg:grid-cols-4 gap-3 mb-6"> |
| 11 | <%= render "admin/stat", label: "Total users", value: number_with_delimiter(@metrics.total_users), |
| 12 | sub: "#{@metrics.admins_count} admin#{'s' unless @metrics.admins_count == 1}" %> |
| 13 | <%= render "admin/stat", label: "New · 7 days", value: "+#{number_with_delimiter(@metrics.new_users_7d)}", accent: "text-green-300" %> |
| 14 | <%= render "admin/stat", label: "New · 30 days", value: "+#{number_with_delimiter(@metrics.new_users_30d)}", accent: "text-green-300" %> |
| 15 | <%= render "admin/stat", label: "Cloud sessions", value: number_with_delimiter(@metrics.cloud_sessions_count) %> |
| 16 | </div> |
| 17 | |
| 18 | <%# Revenue %> |
| 19 | <h2 class="text-xs font-semibold uppercase tracking-wide text-gray-500 mb-2">Recurring revenue</h2> |
| 20 | <div class="grid grid-cols-2 lg:grid-cols-4 gap-3 mb-6"> |
| 21 | <%= render "admin/stat", label: "Estimated MRR", value: admin_usd(@metrics.estimated_mrr), |
| 22 | sub: "active paid plans", accent: "text-brand-300" %> |
| 23 | <%= render "admin/stat", label: "Paying · Pro", value: number_with_delimiter(@metrics.paying("pro")) %> |
| 24 | <%= render "admin/stat", label: "Paying · Team", value: number_with_delimiter(@metrics.paying("team")) %> |
| 25 | <%= render "admin/stat", label: "Trialing", value: number_with_delimiter(@metrics.trialing_count), |
| 26 | sub: (@metrics.past_due_count.positive? ? "#{@metrics.past_due_count} past due" : nil), |
| 27 | accent: (@metrics.past_due_count.positive? ? "text-amber-300" : "text-gray-100") %> |
| 28 | </div> |
| 29 | |
| 30 | <%# Catalog %> |
| 31 | <h2 class="text-xs font-semibold uppercase tracking-wide text-gray-500 mb-2">Catalog</h2> |
| 32 | <div class="grid grid-cols-2 lg:grid-cols-4 gap-3 mb-8"> |
| 33 | <%= render "admin/stat", label: "Repositories", value: number_with_delimiter(@metrics.total_repos) %> |
| 34 | <%= render "admin/stat", label: "Code repos", value: number_with_delimiter(@metrics.code_repos) %> |
| 35 | <%= render "admin/stat", label: "Model repos", value: number_with_delimiter(@metrics.model_repos) %> |
| 36 | <%= render "admin/stat", label: "Cloud reqs · month", value: number_with_delimiter(@metrics.cloud_requests_month), |
| 37 | sub: "#{number_with_delimiter(@metrics.private_repos)} private repos" %> |
| 38 | </div> |
| 39 | |
| 40 | <%# Recent activity %> |
| 41 | <div class="grid grid-cols-1 lg:grid-cols-2 gap-6"> |
| 42 | <section> |
| 43 | <div class="flex items-center justify-between mb-2"> |
| 44 | <h2 class="text-sm font-semibold text-gray-200">Newest users</h2> |
| 45 | <%= link_to "View all →", admin_users_path, class: "text-xs text-brand-400 hover:text-brand-300" %> |
| 46 | </div> |
| 47 | <div class="rounded-lg border border-surface-600 divide-y divide-surface-600 overflow-hidden"> |
| 48 | <% @metrics.recent_users.each do |user| %> |
| 49 | <%= link_to admin_user_path(user.username), class: "flex items-center gap-3 px-3 py-2 hover:bg-surface-700 transition-colors" do %> |
| 50 | <img src="<%= user.avatar_url_or_default %>" alt="" class="w-7 h-7 rounded-full border border-surface-500"> |
| 51 | <div class="min-w-0 flex-1"> |
| 52 | <p class="text-sm text-gray-200 truncate"><%= user.username %><% if user.admin? %> <span class="text-brand-400 text-xs">· admin</span><% end %></p> |
| 53 | <p class="text-xs text-gray-500 truncate"><%= user.email %></p> |
| 54 | </div> |
| 55 | <span class="text-xs text-gray-500 whitespace-nowrap"><%= time_ago_in_words(user.created_at) %> ago</span> |
| 56 | <% end %> |
| 57 | <% end %> |
| 58 | <% if @metrics.recent_users.empty? %><p class="px-3 py-4 text-sm text-gray-500">No users yet.</p><% end %> |
| 59 | </div> |
| 60 | </section> |
| 61 | |
| 62 | <section> |
| 63 | <div class="flex items-center justify-between mb-2"> |
| 64 | <h2 class="text-sm font-semibold text-gray-200">Newest repositories</h2> |
| 65 | <%= link_to "View all →", admin_repositories_path, class: "text-xs text-brand-400 hover:text-brand-300" %> |
| 66 | </div> |
| 67 | <div class="rounded-lg border border-surface-600 divide-y divide-surface-600 overflow-hidden"> |
| 68 | <% @metrics.recent_repos.each do |repo| %> |
| 69 | <%= link_to admin_repository_path(repo.id), class: "flex items-center gap-3 px-3 py-2 hover:bg-surface-700 transition-colors" do %> |
| 70 | <div class="min-w-0 flex-1"> |
| 71 | <p class="text-sm text-gray-200 truncate"><%= repo.user.username %>/<span class="text-gray-100 font-medium"><%= repo.name %></span></p> |
| 72 | <p class="text-xs text-gray-500 truncate"><%= repo.kind %><%= " · private" if repo.is_private %></p> |
| 73 | </div> |
| 74 | <span class="text-xs text-gray-500 whitespace-nowrap"><%= time_ago_in_words(repo.created_at) %> ago</span> |
| 75 | <% end %> |
| 76 | <% end %> |
| 77 | <% if @metrics.recent_repos.empty? %><p class="px-3 py-4 text-sm text-gray-500">No repositories yet.</p><% end %> |
| 78 | </div> |
| 79 | </section> |
| 80 | </div> |