SPB Git forge

spb/rareindex

Public
54commits 1branches 0releases
7.1 MBsize
maindefault branch
10 days agolast push
TypeScript 61.9% HTML 37.2% SQL 0.7%
4.5 KB · 94 lines tsx
Raw Blame History
1import type { Metadata } from 'next';2import Link from 'next/link';3import { requireUser } from '@/lib/auth/session';4import { listTargets } from '@/lib/account/queries';5import { getDisplay } from '@/lib/account/display';6import { deleteTargetAction } from '@/lib/account/actions';7import { PageHeader, btnDanger } from '@/components/account/page-header';8import { Card, CardHeader, EmptyState, Table, th, td, tdNum, Badge } from '@/components/ui/primitives';9import { Progress } from '@/components/account/charts';10import { fmtRelative } from '@/lib/format';11import { TargetForm } from './target-form';1213export const metadata: Metadata = { title: 'Price targets', robots: { index: false } };1415export default async function TargetsPage() {16  const u = await requireUser('/targets');17  const d = await getDisplay();18  const rows = await listTargets(u.id);19  return (20    <>21      <PageHeader title="Price targets" description="Set a RIV level you are waiting for — to buy below or to sell above — and watch progress. You are notified once when it is reached." />22      <div className="grid gap-4 lg:grid-cols-[minmax(0,1fr)_360px]">23        <Card>24          <CardHeader title={`Targets · ${rows.length}`} />25          {rows.length === 0 ? (26            <EmptyState title="No targets yet" description="Add one on the right. Progress is measured from the RIV at the time you set the target." />27          ) : (28            <Table>29              <thead>30                <tr>31                  <th className={th}>Asset</th>32                  <th className={th}>Direction</th>33                  <th className={`${th} text-right`}>Baseline</th>34                  <th className={`${th} text-right`}>Now</th>35                  <th className={`${th} text-right`}>Target</th>36                  <th className={`${th} w-[160px]`}>Progress</th>37                  <th className={th}>Status</th>38                  <th className={th}></th>39                </tr>40              </thead>41              <tbody>42                {rows.map(({ target: t, asset, stats }) => {43                  const now = stats?.rivUsd ?? null;44                  const base = t.baselineUsd ?? now;45                  let progress = 0;46                  if (now !== null && base !== null && base !== t.targetUsd) progress = (now - base) / (t.targetUsd - base);47                  const hit = t.hitAt !== null || (now !== null && (t.direction === 'above' ? now >= t.targetUsd : now <= t.targetUsd));48                  return (49                    <tr key={t.id}>50                      <td className={td}>51                        <div className="max-w-[240px]">52                          <Link href={`/asset/${asset.slug}`} className="block truncate font-medium hover:underline">53                            {asset.title}54                          </Link>55                          <p className="text-[11px] text-subtle">56                            set {fmtRelative(t.createdAt)}57                            {t.note ? ` · ${t.note}` : ''}58                          </p>59                        </div>60                      </td>61                      <td className={td}>62                        <Badge tone={t.direction === 'above' ? 'gain' : 'index'}>{t.direction === 'above' ? 'sell above' : 'buy below'}</Badge>63                      </td>64                      <td className={tdNum}>{t.baselineUsd ? d.money(t.baselineUsd) : '—'}</td>65                      <td className={tdNum}>{now ? d.money(now) : <span className="text-subtle">—</span>}</td>66                      <td className={tdNum}>{d.money(t.targetUsd)}</td>67                      <td className={td}>68                        <Progress value={hit ? 1 : progress} />69                      </td>70                      <td className={td}>{hit ? <Badge tone="gain">reached</Badge> : now === null ? <Badge tone="neutral">no RIV yet</Badge> : <span className="text-xs text-muted">{Math.round(Math.max(0, Math.min(1, progress)) * 100)}%</span>}</td>71                      <td className={td}>72                        <form action={deleteTargetAction}>73                          <input type="hidden" name="id" value={t.id} />74                          <button className={btnDanger}>Remove</button>75                        </form>76                      </td>77                    </tr>78                  );79                })}80              </tbody>81            </Table>82          )}83        </Card>84        <Card>85          <CardHeader title="New target" />86          <div className="p-4">87            <TargetForm />88          </div>89        </Card>90      </div>91    </>92  );93}94