refactor: replace index queries with filter method for database queries, enhance profile management with flash count tracking, and introduce ProfileCard component for improved UI consistency

This commit is contained in:
Ben Allfree
2025-11-24 04:24:10 -08:00
parent 508497f626
commit 10f4ff520a
9 changed files with 167 additions and 106 deletions
+52
View File
@@ -0,0 +1,52 @@
import type { Doc } from '../../convex/_generated/dataModel'
export const profileCardClasses =
'border border-slate-800 rounded-lg p-6 bg-slate-900/50 flex flex-col gap-4'
interface ProfilePillsProps {
version: string
flashCount?: number
flashLabel?: string
}
export function ProfileStatisticPills({
version,
flashCount,
flashLabel,
}: ProfilePillsProps) {
const normalizedCount = flashCount ?? 0
const normalizedLabel =
flashLabel ?? (normalizedCount === 1 ? 'flash' : 'flashes')
return (
<div className="flex items-center justify-between text-xs font-semibold uppercase tracking-wide">
<span className="inline-flex items-center rounded-full bg-slate-800/80 text-slate-200 px-3 py-1">
{version}
</span>
<span className="inline-flex items-center rounded-full bg-cyan-500/10 text-cyan-300 px-3 py-1">
{normalizedCount} {normalizedLabel}
</span>
</div>
)
}
interface ProfileCardContentProps {
profile: Doc<'profiles'>
}
export function ProfileCardContent({ profile }: ProfileCardContentProps) {
const flashCount = profile.flashCount ?? 0
return (
<>
<div className="flex-1">
<h3 className="text-xl font-semibold mb-2">{profile.name}</h3>
<p className="text-slate-300 text-sm leading-relaxed">
{profile.description}
</p>
</div>
<ProfileStatisticPills
version={profile.version}
flashCount={flashCount}
/>
</>
)
}
+7 -13
View File
@@ -3,6 +3,10 @@ import { Plus, Trash2 } from 'lucide-react'
import { useState } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import { toast } from 'sonner'
import {
ProfileCardContent,
profileCardClasses,
} from '@/components/ProfileCard'
import ProfileEditor from '@/components/ProfileEditor'
import { Button } from '@/components/ui/button'
import { api } from '../../convex/_generated/api'
@@ -67,19 +71,9 @@ export default function Dashboard() {
) : (
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{profiles?.map((profile) => (
<div
key={profile._id}
className="border border-slate-800 rounded-lg p-6 bg-slate-900/50"
>
<h3 className="text-xl font-semibold mb-2">{profile.name}</h3>
<p className="text-slate-400 text-sm mb-1">
Version:{' '}
<span className="text-slate-200">{profile.version}</span>
</p>
<p className="text-slate-300 text-sm mb-4 leading-relaxed">
{profile.description}
</p>
<div className="flex gap-2">
<div key={profile._id} className={profileCardClasses}>
<ProfileCardContent profile={profile} />
<div className="flex gap-2 pt-2">
<Button size="sm" asChild>
<Link to={`/profiles/${profile._id}`}>Use</Link>
</Button>
+6 -9
View File
@@ -1,5 +1,9 @@
import { useQuery } from 'convex/react'
import { useNavigate } from 'react-router-dom'
import {
ProfileCardContent,
profileCardClasses,
} from '@/components/ProfileCard'
import { api } from '../../convex/_generated/api'
export default function LandingPage() {
@@ -44,16 +48,9 @@ export default function LandingPage() {
navigate(`/profiles/${profile._id}`)
}
}}
className="border border-slate-800 rounded-lg p-6 bg-slate-900/50 hover:bg-slate-900 cursor-pointer transition-colors text-left"
className={`${profileCardClasses} hover:bg-slate-900 cursor-pointer transition-colors text-left`}
>
<h3 className="text-xl font-semibold mb-2">{profile.name}</h3>
<p className="text-slate-400 text-sm">
Version:{' '}
<span className="text-slate-200">{profile.version}</span>
</p>
<p className="text-slate-300 text-sm mt-3 leading-relaxed">
{profile.description}
</p>
<ProfileCardContent profile={profile} />
</button>
))}
</div>
+15 -5
View File
@@ -3,6 +3,7 @@ import * as React from 'react'
import { useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import { toast } from 'sonner'
import { ProfileStatisticPills } from '@/components/ProfileCard'
import { Button } from '@/components/ui/button'
import { api } from '../../convex/_generated/api'
import type { Id } from '../../convex/_generated/dataModel'
@@ -93,14 +94,23 @@ export default function ProfileDetail() {
}
}
const totalFlashes = profile.flashCount ?? 0
return (
<div className="min-h-screen bg-slate-950 text-white p-8">
<div className="max-w-4xl mx-auto">
<h1 className="text-4xl font-bold mb-2">{profile.name}</h1>
<p className="text-slate-400 mb-8">
Version: {profile.version} Flashed {flashCount} time
{flashCount !== 1 ? 's' : ''}
</p>
<div className="space-y-4 mb-8">
<div>
<h1 className="text-4xl font-bold mb-2">{profile.name}</h1>
<p className="text-slate-400">
Flashed {flashCount} time{flashCount !== 1 ? 's' : ''}
</p>
</div>
<ProfileStatisticPills
version={profile.version}
flashCount={totalFlashes}
/>
</div>
<div className="space-y-8">
{/* Enabled Modules */}
+25 -9
View File
@@ -1,4 +1,4 @@
import { useQuery } from 'convex/react'
import { useMutation, useQuery } from 'convex/react'
import {
ArrowLeft,
CheckCircle,
@@ -7,6 +7,7 @@ import {
XCircle,
} from 'lucide-react'
import { Link, useParams } from 'react-router-dom'
import { ProfileStatisticPills } from '@/components/ProfileCard'
import { Button } from '@/components/ui/button'
import { humanizeStatus } from '@/lib/utils'
import { api } from '../../convex/_generated/api'
@@ -28,6 +29,7 @@ export default function ProfileFlash() {
api.profiles.get,
id ? { id: id as Id<'profiles'> } : 'skip'
)
const recordFlash = useMutation(api.profiles.recordFlash)
if (data === undefined || profile === undefined) {
return (
@@ -79,6 +81,19 @@ export default function ProfileFlash() {
const includedModules = modulesData.modules.filter(
(module) => profile.config?.[module.id] === false
)
const totalFlashes = profile.flashCount ?? 0
const handleDownload = async () => {
if (!id || !build.artifactUrl) return
try {
await recordFlash({ profileId: id as Id<'profiles'> })
} catch (error) {
console.error('Failed to record flash', error)
} finally {
window.open(build.artifactUrl, '_blank', 'noopener,noreferrer')
}
}
const getStatusColor = (status: string) => {
if (status === 'success') return 'text-green-400'
@@ -124,6 +139,10 @@ export default function ProfileFlash() {
<p className="text-slate-200 leading-relaxed">
{profile.description}
</p>
<ProfileStatisticPills
version={profile.version}
flashCount={totalFlashes}
/>
</div>
<div className="bg-slate-900/50 rounded-lg border border-slate-800 p-6">
@@ -176,15 +195,12 @@ export default function ProfileFlash() {
{build.status === 'success' && build.artifactUrl && (
<div>
<a
href={build.artifactUrl}
target="_blank"
rel="noopener noreferrer"
<Button
onClick={handleDownload}
className="bg-cyan-600 hover:bg-cyan-700 w-full"
>
<Button className="bg-cyan-600 hover:bg-cyan-700 w-full">
Download Firmware
</Button>
</a>
Download Firmware
</Button>
</div>
)}
</div>