Trademark Patent Portfolio
blocklegalUSPTO and WIPO intellectual property asset portfolio tracker, patent grant status, jurisdiction coverage, and maintenance fee timeline with docket action workflow.
Installation
$pnpm dlx shadcn-vue@latest add https://uipkge.dev/r/vue/trademark-patent-portfolio.json$npx shadcn-vue@latest add https://uipkge.dev/r/vue/trademark-patent-portfolio.json$yarn dlx shadcn-vue@latest add https://uipkge.dev/r/vue/trademark-patent-portfolio.json$bunx shadcn-vue@latest add https://uipkge.dev/r/vue/trademark-patent-portfolio.jsonNamed registry:
npx shadcn-vue@latest add @uipkge/trademark-patent-portfolioInstalls to:app/components/blocks/Variants
Loading interactive previews…
Loading interactive previews…
Props
| Name | Type / Values | Default | Required |
|---|---|---|---|
class | HTMLAttributes['class'] | — | optional |
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
IpAssetinterface IpAsset {
id: string
identifier: string
officialNumber: string
title: string
type: AssetType
jurisdictionCode: string
jurisdictionName: string
jurisdictionFlag: string
patentOffice: string
applicationDate: string
grantDate: string
status: FilingStatus
nextDeadline: string
deadlineNumber: string
deadlineNote: string
ipcClass: string
attorney: string
gazetteUrl: string
gazetteLabel: string
}npm dependencies
Files installed (1)
app/components/blocks/TrademarkPatentPortfolio.vue26.3 kB
<script setup lang="ts"> import type { HTMLAttributes } from 'vue' import { computed, ref } from 'vue' import { AlertCircle, ArrowUpRight, Award, BadgeCheck, Building2, Calendar, CheckCircle2, ChevronDown, Clock, Copy, Download, ExternalLink, FilePlus2, FileSpreadsheet, FileText, Filter, Globe, MoreHorizontal, Plus, Scale, Search, ShieldCheck, UserCheck, } from 'lucide-vue-next' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' import { Input } from '@/components/ui/input' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' import { cn } from '@/lib/utils' export interface TrademarkPatentPortfolioProps { class?: HTMLAttributes['class'] } const props = defineProps<TrademarkPatentPortfolioProps>() export type AssetType = 'Utility Patent' | 'Trademark' | 'PCT International' export type FilingStatus = 'Granted / Registered' | 'Published / Under Exam' export interface IpAsset { id: string identifier: string officialNumber: string title: string type: AssetType jurisdictionCode: string jurisdictionName: string jurisdictionFlag: string patentOffice: string applicationDate: string grantDate: string status: FilingStatus nextDeadline: string deadlineNumber: string deadlineNote: string ipcClass: string attorney: string gazetteUrl: string gazetteLabel: string } const ipAssets: IpAsset[] = [ { id: 'ip-1', identifier: 'US Patent #11,849,201', officialNumber: 'US 11,849,201 B2', title: 'Zero-Dependency Dual-Framework AST Generation', type: 'Utility Patent', jurisdictionCode: 'US', jurisdictionName: 'United States', jurisdictionFlag: '🇺🇸', patentOffice: 'USPTO', applicationDate: '2023-04-12', grantDate: '2024-06-18', status: 'Granted / Registered', nextDeadline: 'Dec 2026', deadlineNumber: '2026-12-15', deadlineNote: 'USPTO 3.5-Year Maintenance Window', ipcClass: 'CPC G06F 8/41 · Abstract Syntax Trees', attorney: 'Vance & Morrison LLP', gazetteUrl: 'https://patents.google.com/patent/US11849201B2/en', gazetteLabel: 'View USPTO Gazette', }, { id: 'ip-2', identifier: 'US Patent #11,420,184', officialNumber: 'US 11,420,184 B1', title: 'Perceptually Uniform OKLCH Dark Mode Algorithm', type: 'Utility Patent', jurisdictionCode: 'US', jurisdictionName: 'United States', jurisdictionFlag: '🇺🇸', patentOffice: 'USPTO', applicationDate: '2022-09-14', grantDate: '2023-08-22', status: 'Granted / Registered', nextDeadline: 'Feb 2027', deadlineNumber: '2027-02-22', deadlineNote: 'USPTO 3.5-Year Maintenance Window', ipcClass: 'CPC G06T 11/00 · Perceptual Colorimetry', attorney: 'Vance & Morrison LLP', gazetteUrl: 'https://patents.google.com/patent/US11420184B1/en', gazetteLabel: 'View USPTO Gazette', }, { id: 'ip-3', identifier: 'US Trademark #98421042', officialNumber: 'Reg. #7,294,810 (SN 98421042)', title: 'UIPKGE™ (Class 09, 42)', type: 'Trademark', jurisdictionCode: 'US', jurisdictionName: 'United States', jurisdictionFlag: '🇺🇸', patentOffice: 'USPTO', applicationDate: '2023-02-10', grantDate: '2023-11-14', status: 'Granted / Registered', nextDeadline: 'Nov 2029', deadlineNumber: '2029-11-14', deadlineNote: 'Section 8 & 15 Affidavit of Use', ipcClass: 'IC 009, 042 · Computer Software & SaaS', attorney: 'Kilpatrick Townsend & Stockton', gazetteUrl: 'https://tsdr.uspto.gov/', gazetteLabel: 'View USPTO TSDR', }, { id: 'ip-4', identifier: 'WIPO PCT/US2026/018249', officialNumber: 'WO 2026/148201 A1', title: 'Headless Component State Serialization', type: 'PCT International', jurisdictionCode: 'WIPO', jurisdictionName: 'WIPO International', jurisdictionFlag: '🌍', patentOffice: 'WIPO IB', applicationDate: '2026-01-15', grantDate: 'Pending', status: 'Published / Under Exam', nextDeadline: 'Jul 2027', deadlineNumber: '2027-07-15', deadlineNote: '30-Mo National Phase Entry Deadline', ipcClass: 'IPC G06F 9/44 · Component State Transfer', attorney: 'Vance & Morrison LLP', gazetteUrl: 'https://patentscope.wipo.int/', gazetteLabel: 'View WIPO Patentscope', }, { id: 'ip-5', identifier: 'EP Patent #3,948,102', officialNumber: 'EP 3,948,102 A1', title: 'Reactive Virtual DOM Diffing Engine for Web Components', type: 'Utility Patent', jurisdictionCode: 'EU', jurisdictionName: 'European Union', jurisdictionFlag: '🇪🇺', patentOffice: 'EPO', applicationDate: '2024-03-02', grantDate: 'Pending', status: 'Published / Under Exam', nextDeadline: 'Mar 2027', deadlineNumber: '2027-03-31', deadlineNote: 'EPO 3rd Year Annuity Maintenance Fee', ipcClass: 'CPC G06F 9/455 · Virtualized DOM Execution', attorney: 'Bardehle Pagenberg Partnerschaft', gazetteUrl: 'https://worldwide.espacenet.com/', gazetteLabel: 'View EPO Espacenet', }, { id: 'ip-6', identifier: 'EU Trademark #018942001', officialNumber: 'Reg. #018942001', title: 'UIPKGE DESIGN SYSTEM™ (Class 09, 35, 42)', type: 'Trademark', jurisdictionCode: 'EU', jurisdictionName: 'European Union', jurisdictionFlag: '🇪🇺', patentOffice: 'EUIPO', applicationDate: '2023-08-05', grantDate: '2024-01-19', status: 'Granted / Registered', nextDeadline: 'May 2030', deadlineNumber: '2030-05-08', deadlineNote: 'EUIPO 10-Year Trademark Renewal', ipcClass: 'IC 009, 035, 042 · Design Systems & Tooling', attorney: 'Bardehle Pagenberg Partnerschaft', gazetteUrl: 'https://euipo.europa.eu/eSearch/', gazetteLabel: 'View EUIPO eSearch', }, ] const searchQuery = ref('') const selectedType = ref<'all' | AssetType>('all') const selectedStatus = ref<'all' | FilingStatus>('all') function resetFilters() { searchQuery.value = '' selectedType.value = 'all' selectedStatus.value = 'all' } const copiedId = ref<string | null>(null) const feedbackMessage = ref<string | null>(null) const filteredAssets = computed(() => { const query = searchQuery.value.trim().toLowerCase() return ipAssets.filter((asset) => { const matchesType = selectedType.value === 'all' || asset.type === selectedType.value const matchesStatus = selectedStatus.value === 'all' || asset.status === selectedStatus.value const matchesSearch = !query || asset.identifier.toLowerCase().includes(query) || asset.officialNumber.toLowerCase().includes(query) || asset.title.toLowerCase().includes(query) || asset.jurisdictionName.toLowerCase().includes(query) || asset.patentOffice.toLowerCase().includes(query) || asset.ipcClass.toLowerCase().includes(query) return matchesType && matchesStatus && matchesSearch }) }) function copyText(id: string, text: string) { navigator.clipboard?.writeText(text) copiedId.value = id setTimeout(() => { copiedId.value = null }, 2000) } function handleExport() { feedbackMessage.value = 'Exporting IP Portfolio summary (CSV / PDF)...' setTimeout(() => { feedbackMessage.value = null }, 3000) } function handleFileNewAsset() { feedbackMessage.value = 'Opening USPTO / WIPO EFS-Web filing wizard...' setTimeout(() => { feedbackMessage.value = null }, 3000) } function handleAction(action: string, asset: IpAsset) { feedbackMessage.value = `${action}: ${asset.identifier}` setTimeout(() => { feedbackMessage.value = null }, 3000) } </script> <template> <div data-slot="trademark-patent-portfolio" :class="cn('w-full space-y-6', props.class)"> <!-- Header Section --> <Card class="border-border shadow-xs"> <CardHeader class="flex flex-col gap-4 pb-6 lg:flex-row lg:items-center lg:justify-between"> <div class="space-y-1.5"> <div class="flex flex-wrap items-center gap-2"> <Badge variant="outline" class="gap-1.5 font-medium"> <Building2 class="text-muted-foreground size-3.5" aria-hidden="true" /> <span>UIPKGE Technologies Inc.</span> </Badge> <Badge wrap variant="secondary" class="gap-1 font-mono text-xs"> <Scale class="text-primary size-3" aria-hidden="true" /> <span>12 Patents · 6 Registered Trademarks</span> </Badge> </div> <CardTitle class="text-xl font-bold tracking-tight sm:text-2xl"> Intellectual Property & Patent Portfolio </CardTitle> <CardDescription class="text-xs sm:text-sm"> USPTO, EPO, and WIPO asset docket, grant prosecution tracker, and statutory maintenance timeline. </CardDescription> </div> <!-- Action Buttons --> <div class="flex flex-wrap items-center gap-2.5"> <Button aria-label="Download attachment" variant="outline" class="gap-2 shadow-xs" @click="handleExport"> <Download class="size-4" aria-hidden="true" /> <span>Export Portfolio Report</span> </Button> <Button variant="default" class="gap-2 shadow-xs" @click="handleFileNewAsset"> <Plus class="size-4" aria-hidden="true" /> <span>File New IP Asset</span> </Button> </div> </CardHeader> </Card> <!-- Feedback Notification Toast/Banner if active --> <div v-if="feedbackMessage" class="border-primary/30 bg-primary/10 text-primary flex items-center justify-between rounded-lg border px-4 py-2.5 text-xs font-medium shadow-xs transition-colors" > <div class="flex items-center gap-2"> <CheckCircle2 class="size-4 shrink-0" aria-hidden="true" /> <span>{{ feedbackMessage }}</span> </div> <button type="button" @click="feedbackMessage = null" class="text-primary/70 hover:text-primary rounded text-xs underline underline-offset-2" > Dismiss </button> </div> <!-- 4 IP Portfolio KPI Cards Grid --> <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4"> <!-- Card 1: Granted Utility Patents --> <Card class="border-border shadow-xs"> <CardContent class="p-5"> <div class="flex items-center justify-between"> <span class="text-muted-foreground text-xs font-medium">Granted Patents</span> <div class="bg-success/10 text-success flex size-8 items-center justify-center rounded-md"> <Award class="size-4" aria-hidden="true" /> </div> </div> <div class="mt-3"> <div class="text-success text-success text-2xl font-bold tracking-tight tabular-nums"> 8 Patents Granted </div> <p class="text-muted-foreground mt-1 text-xs">6 USPTO · 2 EPO · 100% Enforceable</p> </div> </CardContent> </Card> <!-- Card 2: Pending Applications --> <Card class="border-border shadow-xs"> <CardContent class="p-5"> <div class="flex items-center justify-between"> <span class="text-muted-foreground text-xs font-medium">Pending Examination</span> <div class="bg-warning/10 text-warning flex size-8 items-center justify-center rounded-md"> <Clock class="size-4" aria-hidden="true" /> </div> </div> <div class="mt-3"> <div class="text-warning text-warning text-2xl font-bold tracking-tight tabular-nums"> 4 Under Examination · USPTO </div> <p class="text-muted-foreground mt-1 text-xs">2 PCT International · 1 USPTO · 1 EPO</p> </div> </CardContent> </Card> <!-- Card 3: Registered Trademarks --> <Card class="border-border shadow-xs"> <CardContent class="p-5"> <div class="flex items-center justify-between"> <span class="text-muted-foreground text-xs font-medium">Registered Trademarks</span> <div class="bg-info/10 text-info flex size-8 items-center justify-center rounded-md"> <BadgeCheck class="size-4" aria-hidden="true" /> </div> </div> <div class="mt-3"> <div class="text-info text-info text-2xl font-bold tracking-tight tabular-nums">6 Trademarks Active</div> <p class="text-muted-foreground mt-1 text-xs">Classes 09, 35, 42 · Global Protection</p> </div> </CardContent> </Card> <!-- Card 4: Next Maintenance Fee --> <Card class="border-border shadow-xs"> <CardContent class="p-5"> <div class="flex items-center justify-between"> <span class="text-muted-foreground text-xs font-medium">Next Maintenance Fee</span> <div class="bg-destructive/10 text-destructive flex size-8 items-center justify-center rounded-md"> <Calendar class="size-4" aria-hidden="true" /> </div> </div> <div class="mt-3"> <div class="text-destructive text-destructive text-2xl font-bold tracking-tight tabular-nums"> Due Dec 15, 2026 · USPTO 3.5-Year Window </div> <p class="text-muted-foreground mt-1 text-xs">Est. fee $2,000 · Surcharge window opens</p> </div> </CardContent> </Card> </div> <!-- Filters & Table Container Card --> <Card class="border-border shadow-xs"> <CardHeader class="border-border border-b pb-4"> <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"> <div> <CardTitle class="text-base font-semibold">Registered Intellectual Property Assets</CardTitle> <CardDescription class="text-xs"> Showing {{ filteredAssets.length }} of {{ ipAssets.length }} assets across USPTO, EUIPO, and WIPO </CardDescription> </div> <!-- Controls: Search and Filters --> <div class="flex flex-wrap items-center gap-2"> <div class="relative min-w-[200px] flex-1 sm:w-64"> <Search aria-hidden="true" class="text-muted-foreground pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2" /> <Input v-model="searchQuery" type="text" placeholder="Search patent #, title, class..." class="h-8 pl-8 text-xs" /> </div> <!-- Type Filter Dropdown --> <DropdownMenu> <DropdownMenuTrigger as-child> <Button variant="outline" size="sm" class="h-8 gap-1.5 text-xs"> <Filter class="size-3.5 opacity-70" aria-hidden="true" /> <span>{{ selectedType === 'all' ? 'Type: All' : selectedType }}</span> <ChevronDown class="size-3 opacity-50" aria-hidden="true" /> </Button> </DropdownMenuTrigger> <DropdownMenuContent align="end" class="w-48"> <DropdownMenuLabel class="text-xs">Filter by Type</DropdownMenuLabel> <DropdownMenuSeparator /> <DropdownMenuItem @click="selectedType = 'all'">All Types</DropdownMenuItem> <DropdownMenuItem @click="selectedType = 'Utility Patent'">Utility Patent</DropdownMenuItem> <DropdownMenuItem @click="selectedType = 'Trademark'">Trademark</DropdownMenuItem> <DropdownMenuItem @click="selectedType = 'PCT International'">PCT International</DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> <!-- Status Filter Dropdown --> <DropdownMenu> <DropdownMenuTrigger as-child> <Button variant="outline" size="sm" class="h-8 gap-1.5 text-xs"> <span>{{ selectedStatus === 'all' ? 'Status: All' : selectedStatus }}</span> <ChevronDown class="size-3 opacity-50" aria-hidden="true" /> </Button> </DropdownMenuTrigger> <DropdownMenuContent align="end" class="w-52"> <DropdownMenuLabel class="text-xs">Filter by Status</DropdownMenuLabel> <DropdownMenuSeparator /> <DropdownMenuItem @click="selectedStatus = 'all'">All Statuses</DropdownMenuItem> <DropdownMenuItem @click="selectedStatus = 'Granted / Registered'" >Granted / Registered</DropdownMenuItem > <DropdownMenuItem @click="selectedStatus = 'Published / Under Exam'" >Published / Under Exam</DropdownMenuItem > </DropdownMenuContent> </DropdownMenu> </div> </div> </CardHeader> <CardContent class="p-0"> <div class="overflow-x-auto"> <Table> <TableHeader class="bg-muted/40"> <TableRow> <TableHead class="min-w-[280px]">Asset Identifier & Title</TableHead> <TableHead class="w-[140px]">Type</TableHead> <TableHead class="w-[160px]">Jurisdiction</TableHead> <TableHead class="w-[160px]">App / Grant Date</TableHead> <TableHead class="w-[170px]">Filing Status</TableHead> <TableHead class="w-[180px]">Next Renewal / Deadline</TableHead> <TableHead class="w-[70px] text-right"><span class="sr-only">Actions</span></TableHead> </TableRow> </TableHeader> <TableBody> <TableRow v-for="asset in filteredAssets" :key="asset.id" class="hover:bg-muted/40 transition-colors"> <!-- Asset Identifier & Title --> <TableCell class="py-3.5 align-top"> <div class="space-y-1"> <div class="flex flex-wrap items-center gap-1.5"> <span class="text-foreground font-mono text-xs font-bold tracking-tight"> {{ asset.identifier }} </span> <button type="button" @click="copyText(asset.id, asset.officialNumber)" class="text-muted-foreground hover:text-foreground focus-visible:ring-ring inline-flex items-center gap-1 rounded p-0.5 transition-colors focus-visible:ring-2 focus-visible:outline-hidden" :title="`Copy ${asset.officialNumber}`" > <Check v-if="copiedId === asset.id" class="text-success size-3" aria-hidden="true" /> <Copy v-else class="size-3" aria-hidden="true" /> </button> </div> <div class="text-foreground text-xs font-medium"> {{ asset.title }} </div> <div class="text-muted-foreground flex flex-wrap items-center gap-2 text-xs"> <span>{{ asset.ipcClass }}</span> <span>•</span> <span>Counsel: {{ asset.attorney }}</span> </div> </div> </TableCell> <!-- Type Badge --> <TableCell class="align-top"> <Badge variant="outline" :class=" cn('font-mono text-xs font-medium', { 'border-chart-1/30 bg-chart-1/10 text-chart-1': asset.type === 'Utility Patent', 'border-info/30 bg-info/10 text-info': asset.type === 'Trademark', 'border-warning/30 bg-warning/10 text-warning': asset.type === 'PCT International', }) " > {{ asset.type }} </Badge> </TableCell> <!-- Jurisdiction Flag & Office --> <TableCell class="align-top"> <div class="flex items-center gap-2"> <span class="text-base leading-none" :aria-label="asset.jurisdictionName"> {{ asset.jurisdictionFlag }} </span> <div> <div class="text-foreground text-xs font-medium"> {{ asset.jurisdictionName }} </div> <div class="text-muted-foreground font-mono text-xs"> {{ asset.patentOffice }} </div> </div> </div> </TableCell> <!-- Application / Grant Date in tabular-nums --> <TableCell class="align-top"> <div class="space-y-0.5 font-mono text-xs tabular-nums"> <div class="text-muted-foreground flex items-center gap-1.5"> <span class="text-muted-foreground/70">Filing:</span> <span class="text-foreground font-medium">{{ asset.applicationDate }}</span> </div> <div class="text-muted-foreground flex items-center gap-1.5"> <span class="text-muted-foreground/70">Grant:</span> <span :class=" asset.grantDate === 'Pending' ? 'text-warning font-medium' : 'text-foreground font-medium' " > {{ asset.grantDate }} </span> </div> </div> </TableCell> <!-- Filing Status --> <TableCell class="align-top"> <div v-if="asset.status === 'Granted / Registered'" class="border-success/30 bg-success/10 text-success inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-medium" > <span class="relative flex size-1.5"> <span class="bg-success absolute inline-flex h-full w-full rounded-full opacity-75"></span> <span class="bg-success relative inline-flex size-1.5 rounded-full"></span> </span> <span>Granted / Registered</span> </div> <div v-else class="border-warning/30 bg-warning/10 text-warning inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-medium" > <Clock class="text-warning size-3" aria-hidden="true" /> <span>Published / Under Exam</span> </div> </TableCell> <!-- Next Renewal / Maintenance Deadline --> <TableCell class="align-top"> <div class="space-y-0.5"> <div class="text-foreground flex items-center gap-1.5 font-mono text-xs font-semibold tabular-nums"> <Calendar class="text-muted-foreground size-3.5" aria-hidden="true" /> <span>{{ asset.nextDeadline }}</span> </div> <p class="text-muted-foreground text-xs leading-tight"> {{ asset.deadlineNote }} </p> </div> </TableCell> <!-- Actions Dropdown --> <TableCell class="py-3.5 text-right align-top"> <DropdownMenu> <DropdownMenuTrigger as-child> <Button variant="ghost" size="icon-sm" class="text-muted-foreground hover:text-foreground focus-visible:ring-ring" > <MoreHorizontal class="size-4" aria-hidden="true" /> <span class="sr-only">Open actions for {{ asset.identifier }}</span> </Button> </DropdownMenuTrigger> <DropdownMenuContent align="end" class="w-56"> <DropdownMenuLabel class="text-xs">Asset Operations</DropdownMenuLabel> <DropdownMenuSeparator /> <DropdownMenuItem @click="handleAction(asset.gazetteLabel, asset)"> <ExternalLink class="mr-2 size-4" aria-hidden="true" /> <span>{{ asset.gazetteLabel }}</span> </DropdownMenuItem> <DropdownMenuItem @click="handleAction('Download Grant Certificate', asset)"> <Download class="mr-2 size-4" aria-hidden="true" /> <span>Download Grant Certificate</span> </DropdownMenuItem> <DropdownMenuItem @click="handleAction('Assign Counsel', asset)"> <UserCheck class="mr-2 size-4" aria-hidden="true" /> <span>Assign Counsel</span> </DropdownMenuItem> <DropdownMenuSeparator /> <DropdownMenuItem @click="copyText(asset.id, asset.officialNumber)"> <Copy class="mr-2 size-4" aria-hidden="true" /> <span>Copy Docket Number</span> </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> </TableCell> </TableRow> <!-- Empty Filter State --> <TableRow v-if="filteredAssets.length === 0"> <TableCell colspan="7" class="text-muted-foreground py-10 text-center text-xs"> <div class="flex flex-col items-center justify-center gap-2"> <FileText class="text-muted-foreground/60 size-8" aria-hidden="true" /> <p class="font-medium">No intellectual property assets match your search filters.</p> <Button variant="outline" size="sm" class="mt-1 text-xs" @click="resetFilters"> Reset Filters </Button> </div> </TableCell> </TableRow> </TableBody> </Table> </div> </CardContent> </Card> </div> </template>
Raw manifest:https://uipkge.dev/r/vue/trademark-patent-portfolio.json