-
Notifications
You must be signed in to change notification settings - Fork 12
feat: ui-ux mockups for backoffice application #65
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| import { SectionTitle } from "@/components/shared/section-title"; | ||
| import { ManageLoansView } from "@/features/campaigns/components/loans/manage-loans-view"; | ||
|
|
||
| interface Props { | ||
| params: Promise<{ id: string }>; | ||
| } | ||
|
|
||
| export default async function CampaignLoansPage({ params }: Props) { | ||
| const { id } = await params; | ||
|
|
||
| return ( | ||
| <div className="flex flex-col gap-6"> | ||
| <SectionTitle | ||
| title="Manejar Préstamos" | ||
| description={`Administra los hitos y préstamos de la campaña #${id.slice(0, 3).toUpperCase()}.`} | ||
| /> | ||
| <ManageLoansView /> | ||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import { SectionTitle } from "@/components/shared/section-title"; | ||
| import { ManageLoansView } from "@/features/campaigns/components/loans/manage-loans-view"; | ||
|
|
||
| export default function ManageLoansPage() { | ||
| return ( | ||
| <div className="flex flex-col gap-6"> | ||
| <SectionTitle | ||
| title="Gestionar Préstamos" | ||
| description="Administra los hitos y préstamos activos de la campaña." | ||
| /> | ||
| <ManageLoansView /> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import { SectionTitle } from "@/components/shared/section-title"; | ||
| import { CreateCampaignStepper } from "@/features/campaigns/components/create/create-campaign-stepper"; | ||
|
|
||
| export default function NewCampaignPage() { | ||
| return ( | ||
| <div className="flex flex-col gap-6"> | ||
| <SectionTitle | ||
| title="Nueva Campaña" | ||
| description="Completa los pasos para configurar tu programa de micro-préstamos." | ||
| /> | ||
| <CreateCampaignStepper /> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| import Link from "next/link"; | ||
| import { SectionTitle } from "@/components/shared/section-title"; | ||
| import { CampaignsView } from "@/features/campaigns/components/campaigns-view"; | ||
| import { Button } from "@tokenization/ui/button"; | ||
| import { Plus } from "lucide-react"; | ||
|
|
||
| export default function CampaignsPage() { | ||
| return ( | ||
| <div className="flex flex-col gap-6"> | ||
| <div className="flex flex-wrap justify-between gap-1"> | ||
| <SectionTitle | ||
| title="Campañas" | ||
| description="Consulta y gestiona las campañas de inversión activas." | ||
| /> | ||
| <div className="flex gap-2"> | ||
|
|
||
| <Button size="lg" asChild> | ||
| <Link href="/campaigns/new"> | ||
| <Plus size={16} /> | ||
| Nueva Campaña | ||
| </Link> | ||
| </Button> | ||
| </div> | ||
| </div> | ||
| <CampaignsView /> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import type { ReactNode } from "react"; | ||
| import { PageShell } from "@/components/layout/page-shell"; | ||
|
|
||
| export default function DashboardLayout({ children }: { children: ReactNode }) { | ||
| return <PageShell>{children}</PageShell>; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import { SectionTitle } from "@/components/shared/section-title"; | ||
| import { RoiView } from "@/features/campaigns/components/roi/roi-view"; | ||
|
|
||
| export default function RoiPage() { | ||
| return ( | ||
| <div className="flex flex-col gap-6"> | ||
| <SectionTitle | ||
| title="Retornos" | ||
| description="Gestione y monitoree sus programas de ROI activos en tiempo real." | ||
| /> | ||
| <RoiView /> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,4 @@ | ||
| import type { Metadata } from "next"; | ||
| import { Geist, Geist_Mono } from "next/font/google"; | ||
| import "./globals.css"; | ||
| import { ReactQueryClientProvider } from "@tokenization/tw-blocks-shared/src/providers/ReactQueryClientProvider"; | ||
| import { TrustlessWorkProvider } from "@tokenization/tw-blocks-shared/src/providers/TrustlessWork"; | ||
|
|
@@ -9,21 +8,13 @@ import { EscrowAmountProvider } from "@tokenization/tw-blocks-shared/src/provide | |
| import { Toaster } from "@tokenization/ui/sonner"; | ||
| import { WalletProvider } from "@tokenization/tw-blocks-shared/src/wallet-kit/WalletProvider"; | ||
| import type { ReactNode } from "react"; | ||
| import { Header } from "@/components/shared/Header"; | ||
| import { Space_Grotesk } from "next/font/google"; | ||
| import localFont from "next/font/local"; | ||
| import { Inter } from "next/font/google"; | ||
| import { cn } from "@/lib/utils"; | ||
|
|
||
| const Exo2 = localFont({ | ||
| src: "./fonts/Exo2.ttf", | ||
| variable: "---exo-2", | ||
| weight: "100 900", | ||
| display: "swap", | ||
| }); | ||
|
|
||
| const spaceGrotesk = Space_Grotesk({ | ||
| const inter = Inter({ | ||
| subsets: ["latin"], | ||
| display: "swap", | ||
| variable: "--font-inter", | ||
| }); | ||
|
Comment on lines
+11
to
18
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Description: Verify Tailwind config has fontFamily.sans configured with --font-inter variable
# Search for tailwind config files and check fontFamily configuration
fd -e js -e ts -e mjs 'tailwind.config' apps/backoffice-tokenization --exec cat {} \; 2>/dev/null || \
fd -e js -e ts -e mjs 'tailwind.config' --max-depth 2 --exec cat {} \;Repository: Trustless-Work/tokenization-private-credit Length of output: 454 Add fontFamily configuration to Tailwind config to apply Inter font. The Inter font is configured with CSS variable theme: {
extend: {
fontFamily: {
sans: ['var(--font-inter)'],
},
},
},🤖 Prompt for AI Agents |
||
|
|
||
| export const metadata: Metadata = { | ||
|
|
@@ -38,29 +29,14 @@ export default function RootLayout({ | |
| }>) { | ||
| return ( | ||
| <html lang="en"> | ||
| <body | ||
| className={cn( | ||
| Exo2.variable, | ||
| "antialiased dark", | ||
| spaceGrotesk.className | ||
| )} | ||
| > | ||
| <body className={cn(inter.variable, "antialiased font-sans")}> | ||
| <ReactQueryClientProvider> | ||
| <TrustlessWorkProvider> | ||
| <WalletProvider> | ||
| <EscrowProvider> | ||
| <EscrowDialogsProvider> | ||
| <EscrowAmountProvider> | ||
| <div className="relative flex min-h-screen w-full"> | ||
| <div className="flex-1 flex flex-col w-full"> | ||
| <div className="container mx-auto"> | ||
| <Header /> | ||
|
|
||
| {children} | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
| {children} | ||
| <Toaster position="top-right" richColors /> | ||
| </EscrowAmountProvider> | ||
| </EscrowDialogsProvider> | ||
|
|
||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,11 @@ | ||
| import { HomeView } from "@/features/home/HomeView"; | ||
| import { Header } from "@/components/shared/Header"; | ||
|
|
||
| export default function Home() { | ||
| return <HomeView />; | ||
| return ( | ||
| <div className="container mx-auto"> | ||
| <Header /> | ||
| <HomeView /> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| "use client"; | ||
|
|
||
| import { SidebarTrigger } from "@tokenization/ui/sidebar"; | ||
|
|
||
| export function AppHeader() { | ||
| return ( | ||
| <header className="flex h-14 shrink-0 items-center border-b border-border bg-card px-4 md:hidden"> | ||
| <SidebarTrigger /> | ||
| </header> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| "use client"; | ||
|
|
||
| import Image from "next/image"; | ||
| import { TrendingUp, Wallet } from "lucide-react"; | ||
| import { | ||
| AppSidebar as SharedAppSidebar, | ||
| type AppSidebarNavItem, | ||
| type AppSidebarLogoConfig, | ||
| } from "@tokenization/ui/app-sidebar"; | ||
| import { SidebarWalletButton } from "@tokenization/ui/sidebar-wallet-button"; | ||
|
|
||
| const logo: AppSidebarLogoConfig = { | ||
| element: ( | ||
| <Image | ||
| src="/interactuar_logo.png" | ||
| alt="interactuar" | ||
| width={160} | ||
| height={32} | ||
| priority | ||
| style={{ objectFit: "contain" }} | ||
| /> | ||
| ), | ||
| href: "/", | ||
| }; | ||
|
|
||
| const navItems: AppSidebarNavItem[] = [ | ||
| { | ||
| label: "Campañas", | ||
| href: "/campaigns", | ||
| icon: Wallet, | ||
| }, | ||
| { | ||
| label: "Retorno de Inversión", | ||
| href: "/roi", | ||
| icon: TrendingUp, | ||
| }, | ||
| ]; | ||
|
|
||
| export function AppSidebar() { | ||
| return ( | ||
| <SharedAppSidebar | ||
| navItems={navItems} | ||
| logo={logo} | ||
| footerContent={<SidebarWalletButton />} | ||
| /> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| import type { ReactNode } from "react"; | ||
| import { AppSidebar } from "@/components/layout/app-sidebar"; | ||
| import { AppHeader } from "@/components/layout/app-header"; | ||
| import { SidebarInset, SidebarProvider } from "@tokenization/ui/sidebar"; | ||
|
|
||
| interface PageShellProps { | ||
| children: ReactNode; | ||
| } | ||
|
|
||
| export function PageShell({ children }: PageShellProps) { | ||
| return ( | ||
| <SidebarProvider> | ||
| <AppSidebar /> | ||
| <SidebarInset> | ||
| <AppHeader /> | ||
| <div className="flex flex-1 flex-col gap-4 p-6">{children}</div> | ||
| </SidebarInset> | ||
| </SidebarProvider> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| "use client"; | ||
|
|
||
| import Link from "next/link"; | ||
| import { Badge } from "@tokenization/ui/badge"; | ||
| import { Button } from "@tokenization/ui/button"; | ||
| import { Progress } from "@tokenization/ui/progress"; | ||
| import { cn } from "@tokenization/shared/lib/utils"; | ||
| import { ExternalLink, Landmark } from "lucide-react"; | ||
| import type { Campaign } from "@/features/campaigns/types/campaign.types"; | ||
| import { CAMPAIGN_STATUS_CONFIG } from "@/features/campaigns/constants/campaign-status"; | ||
| import { mapCampaignProgress } from "@/features/campaigns/utils/campaign.mapper"; | ||
|
|
||
| interface CampaignCardProps { | ||
| campaign: Campaign; | ||
| location?: string; | ||
| organization?: string; | ||
| participants?: number; | ||
| onSeeEscrow?: () => void; | ||
| } | ||
|
|
||
| export function CampaignCard({ | ||
| campaign, | ||
| location, | ||
| organization, | ||
| participants = 0, | ||
| onSeeEscrow, | ||
| }: CampaignCardProps) { | ||
| const { title, description, status, targetAmount, raisedAmount, id } = campaign; | ||
|
Comment on lines
+21
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unused props and variables. The following are destructured but never used in the rendered output:
If these are placeholders for future implementation, consider removing them until needed to avoid confusion. 🧹 Proposed cleanup export function CampaignCard({
campaign,
- location,
- organization,
- participants = 0,
onSeeEscrow,
}: CampaignCardProps) {
- const { title, description, status, targetAmount, raisedAmount, id } = campaign;
+ const { title, description, status, raisedAmount, id } = campaign;Also update the interface if these props are not needed: interface CampaignCardProps {
campaign: Campaign;
- location?: string;
- organization?: string;
- participants?: number;
onSeeEscrow?: () => void;
}🤖 Prompt for AI Agents |
||
|
|
||
| const progress = mapCampaignProgress(campaign); | ||
|
|
||
| const statusCfg = CAMPAIGN_STATUS_CONFIG[status]; | ||
|
|
||
| return ( | ||
| <div | ||
| className={cn( | ||
| "flex flex-col gap-4 rounded-xl border border-border bg-card p-5", | ||
| "shadow-card hover:shadow-hover", | ||
| "transition-shadow duration-200" | ||
| )} | ||
| > | ||
| {/* Top row: status + action */} | ||
| <div className="flex items-center justify-between"> | ||
| <Badge | ||
| variant="outline" | ||
| className={cn("text-xs font-semibold uppercase tracking-wide", statusCfg.className)} | ||
| > | ||
| {statusCfg.label} | ||
| </Badge> | ||
|
|
||
| <Button size="sm" className="cursor-pointer gap-1.5" asChild> | ||
| <Link href={`/campaigns/${id}/loans`}> | ||
| <Landmark className="size-3.5" /> | ||
| Manejar Préstamos | ||
| </Link> | ||
| </Button> | ||
| </div> | ||
|
|
||
| {/* Title & subtitle */} | ||
| <div className="flex flex-col gap-0.5"> | ||
| <h3 className="text-lg font-bold text-foreground leading-tight"> | ||
| #{id.slice(0, 3).toUpperCase()} {title} | ||
| </h3> | ||
| </div> | ||
|
|
||
| {/* Description */} | ||
| <p className="text-sm text-text-secondary leading-relaxed line-clamp-2"> | ||
| {description} | ||
| </p> | ||
|
|
||
| {/* Bottom row: participants + escrow link | progress */} | ||
| <div className="flex items-end justify-between gap-4 pt-1"> | ||
| {/* Participants + escrow */} | ||
| <div className="flex items-center gap-2"> | ||
| <Button | ||
| variant="ghost" | ||
| onClick={onSeeEscrow} | ||
| className="flex items-center gap-1 text-xs font-semibold text-primary hover:text-primary/80 transition-colors cursor-pointer" | ||
| > | ||
| See Escrow | ||
| <ExternalLink className="size-3" /> | ||
| </Button> | ||
| </div> | ||
|
|
||
| {/* Progress */} | ||
| <div className="flex flex-col items-end gap-1.5 min-w-40"> | ||
| <div className="flex items-center justify-between w-full"> | ||
| <span className="text-[10px] font-semibold uppercase tracking-widest text-text-muted"> | ||
| Loans Completed | ||
| </span> | ||
| <span className="text-xs font-bold text-foreground">{progress}%</span> | ||
| </div> | ||
| <Progress value={progress} className="h-1.5 w-full" /> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| interface SectionTitleProps { | ||
| title: string; | ||
| description?: string; | ||
| } | ||
|
|
||
| export function SectionTitle({ title, description }: SectionTitleProps) { | ||
| return ( | ||
| <div className="flex flex-col gap-1"> | ||
| <h2 className="text-3xl font-bold text-foreground">{title}</h2> | ||
| {description && ( | ||
| <p className="text-md text-text-secondary">{description}</p> | ||
| )} | ||
| </div> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thread the route
idinto the loans view.Right now this route is campaign-scoped only in the heading;
ManageLoansViewgets noid, so every/campaigns/[id]/loanspage will render the same unscoped state. Please pass the campaign id into the view/hook before wiring real fetches or mutations.🔧 Minimal shape
📝 Committable suggestion
🤖 Prompt for AI Agents