mirror of
https://github.com/MeshEnvy/mesh-forge.git
synced 2026-08-12 11:53:00 +02:00
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:
@@ -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
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 */}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user