@notis_ai/cli 0.2.7 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/README.md +15 -0
  2. package/dist/scaffolds/notis-database/app/page.tsx +38 -41
  3. package/dist/scaffolds/notis-database/lib/types.ts +5 -7
  4. package/dist/scaffolds/notis-database/metadata/screenshot-1.png +0 -0
  5. package/dist/scaffolds/notis-database/metadata/screenshot-2.png +0 -0
  6. package/dist/scaffolds/notis-database/metadata/screenshot-3.png +0 -0
  7. package/dist/scaffolds/notis-database/metadata/screenshot-4.png +0 -0
  8. package/dist/scaffolds/notis-database/metadata/screenshot-5.png +0 -0
  9. package/dist/scaffolds/notis-database/metadata/screenshot-fixtures.json +1839 -0
  10. package/dist/scaffolds/notis-database/notis.config.ts +48 -1
  11. package/dist/scaffolds/notis-database/package-lock.json +3935 -0
  12. package/dist/scaffolds/notis-database/package.json +2 -1
  13. package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/package.json +4 -0
  14. package/dist/scaffolds/notis-database/packages/sdk/src/components/DocumentEditor.tsx +93 -0
  15. package/dist/scaffolds/notis-database/packages/sdk/src/components/Markdown.tsx +60 -0
  16. package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/components/MultiSelectActionBar.tsx +7 -2
  17. package/dist/scaffolds/notis-database/packages/sdk/src/config.ts +196 -0
  18. package/dist/scaffolds/notis-database/packages/sdk/src/documents.ts +229 -0
  19. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDatabaseSchema.ts +85 -0
  20. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocument.ts +78 -0
  21. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocuments.ts +121 -0
  22. package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useMultiSelect.ts +38 -2
  23. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useUpsertDocument.ts +95 -0
  24. package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/index.ts +30 -1
  25. package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/runtime.ts +76 -17
  26. package/dist/scaffolds/notis-database/packages/sdk/src/styles.css +186 -0
  27. package/dist/scaffolds/notis-database/src/mock-runtime.ts +4 -1
  28. package/dist/scaffolds/notis-journal/CHANGELOG.md +4 -0
  29. package/dist/scaffolds/notis-journal/app/page.tsx +1 -0
  30. package/dist/scaffolds/notis-journal/package-lock.json +4615 -0
  31. package/dist/scaffolds/notis-journal/package.json +2 -2
  32. package/dist/scaffolds/notis-journal/packages/sdk/src/config.ts +52 -1
  33. package/dist/scaffolds/notis-notes/app/page.tsx +39 -46
  34. package/dist/scaffolds/notis-notes/app/phosphor-icons.ts +596 -0
  35. package/dist/scaffolds/notis-notes/lib/visible-properties.ts +144 -0
  36. package/dist/scaffolds/notis-notes/metadata/screenshot-1.png +0 -0
  37. package/dist/scaffolds/notis-notes/metadata/screenshot-2.png +0 -0
  38. package/dist/scaffolds/notis-notes/metadata/screenshot-3.png +0 -0
  39. package/dist/scaffolds/notis-notes/metadata/screenshot-4.png +0 -0
  40. package/dist/scaffolds/notis-notes/metadata/screenshot-5.png +0 -0
  41. package/dist/scaffolds/notis-notes/metadata/screenshot-fixtures.json +752 -0
  42. package/dist/scaffolds/notis-notes/notis.config.ts +48 -3
  43. package/dist/scaffolds/notis-notes/package-lock.json +4636 -0
  44. package/dist/scaffolds/notis-notes/package.json +7 -3
  45. package/dist/scaffolds/notis-notes/packages/sdk/package.json +36 -0
  46. package/dist/scaffolds/notis-notes/packages/sdk/src/components/DocumentEditor.tsx +93 -0
  47. package/dist/scaffolds/notis-notes/packages/sdk/src/components/Markdown.tsx +60 -0
  48. package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectActionBar.tsx +278 -0
  49. package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectCheckbox.tsx +91 -0
  50. package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectDragOverlay.tsx +39 -0
  51. package/dist/scaffolds/notis-notes/packages/sdk/src/config.ts +196 -0
  52. package/dist/scaffolds/notis-notes/packages/sdk/src/documents.ts +229 -0
  53. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useBackend.ts +41 -0
  54. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDatabaseSchema.ts +85 -0
  55. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocument.ts +78 -0
  56. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocuments.ts +121 -0
  57. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useMultiSelect.ts +539 -0
  58. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotis.ts +34 -0
  59. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotisNavigation.ts +49 -0
  60. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTool.ts +64 -0
  61. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTools.ts +56 -0
  62. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTopBarSearch.ts +73 -0
  63. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useUpsertDocument.ts +95 -0
  64. package/dist/scaffolds/notis-notes/packages/sdk/src/index.ts +83 -0
  65. package/dist/scaffolds/notis-notes/packages/sdk/src/provider.tsx +43 -0
  66. package/dist/scaffolds/notis-notes/packages/sdk/src/runtime.ts +220 -0
  67. package/dist/scaffolds/notis-notes/packages/sdk/src/styles.css +186 -0
  68. package/dist/scaffolds/notis-notes/packages/sdk/src/ui.ts +15 -0
  69. package/dist/scaffolds/notis-notes/packages/sdk/src/vite.ts +56 -0
  70. package/dist/scaffolds/notis-notes/packages/sdk/tsconfig.json +15 -0
  71. package/dist/scaffolds/notis-random/CHANGELOG.md +12 -2
  72. package/dist/scaffolds/notis-random/app/history/page.tsx +5 -4
  73. package/dist/scaffolds/notis-random/app/page.tsx +141 -74
  74. package/dist/scaffolds/notis-random/components/ui/button.tsx +50 -0
  75. package/dist/scaffolds/notis-random/components/ui/card.tsx +16 -0
  76. package/dist/scaffolds/notis-random/components/ui/input.tsx +23 -0
  77. package/dist/scaffolds/notis-random/lib/notis-tools.ts +26 -7
  78. package/dist/scaffolds/notis-random/lib/rng.ts +179 -19
  79. package/dist/scaffolds/notis-random/lib/roll-record.ts +103 -16
  80. package/dist/scaffolds/notis-random/metadata/screenshot-1.png +0 -0
  81. package/dist/scaffolds/notis-random/metadata/screenshot-2.png +0 -0
  82. package/dist/scaffolds/notis-random/metadata/screenshot-3.png +0 -0
  83. package/dist/scaffolds/notis-random/metadata/screenshot-4.png +0 -0
  84. package/dist/scaffolds/notis-random/metadata/screenshot-5.png +0 -0
  85. package/dist/scaffolds/notis-random/metadata/screenshot-fixtures.json +753 -0
  86. package/dist/scaffolds/notis-random/notis.config.ts +47 -3
  87. package/dist/scaffolds/notis-random/package-lock.json +4513 -0
  88. package/dist/scaffolds/notis-random/package.json +6 -2
  89. package/dist/scaffolds/notis-random/packages/sdk/package.json +36 -0
  90. package/dist/scaffolds/notis-random/packages/sdk/src/components/DocumentEditor.tsx +93 -0
  91. package/dist/scaffolds/notis-random/packages/sdk/src/components/Markdown.tsx +60 -0
  92. package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectActionBar.tsx +278 -0
  93. package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectCheckbox.tsx +91 -0
  94. package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectDragOverlay.tsx +39 -0
  95. package/dist/scaffolds/notis-random/packages/sdk/src/config.ts +196 -0
  96. package/dist/scaffolds/notis-random/packages/sdk/src/documents.ts +229 -0
  97. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useBackend.ts +41 -0
  98. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDatabaseSchema.ts +85 -0
  99. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocument.ts +78 -0
  100. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocuments.ts +121 -0
  101. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useMultiSelect.ts +539 -0
  102. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotis.ts +34 -0
  103. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotisNavigation.ts +49 -0
  104. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTool.ts +64 -0
  105. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTools.ts +56 -0
  106. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTopBarSearch.ts +73 -0
  107. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useUpsertDocument.ts +95 -0
  108. package/dist/scaffolds/notis-random/packages/sdk/src/index.ts +83 -0
  109. package/dist/scaffolds/notis-random/packages/sdk/src/provider.tsx +43 -0
  110. package/dist/scaffolds/notis-random/packages/sdk/src/runtime.ts +220 -0
  111. package/dist/scaffolds/notis-random/packages/sdk/src/styles.css +186 -0
  112. package/dist/scaffolds/notis-random/packages/sdk/src/ui.ts +15 -0
  113. package/dist/scaffolds/notis-random/packages/sdk/src/vite.ts +56 -0
  114. package/dist/scaffolds/notis-random/packages/sdk/tsconfig.json +15 -0
  115. package/dist/scaffolds/notis-random/tailwind.config.ts +8 -1
  116. package/dist/scaffolds.json +8 -17
  117. package/package.json +2 -1
  118. package/skills/notis-apps/SKILL.md +449 -144
  119. package/skills/notis-apps/cli.md +15 -0
  120. package/skills/notis-cli/SKILL.md +8 -4
  121. package/skills/notis-onboarding/BRIEF.md +93 -0
  122. package/src/cli.js +50 -12
  123. package/src/command-specs/apps.js +154 -21
  124. package/src/command-specs/index.js +2 -0
  125. package/src/command-specs/onboarding.js +216 -0
  126. package/src/command-specs/tools.js +8 -4
  127. package/src/runtime/app-platform.js +65 -4
  128. package/src/runtime/desktop-auth.js +66 -17
  129. package/src/runtime/errors.js +1 -0
  130. package/src/runtime/output.js +9 -2
  131. package/src/runtime/profiles.js +179 -17
  132. package/src/runtime/telemetry.js +92 -0
  133. package/src/runtime/transport.js +26 -1
  134. package/template/.harness/index.html.tmpl +30 -2
  135. package/template/packages/sdk/src/config.ts +38 -1
  136. package/dist/scaffolds/notis-affiliate-prospects/CHANGELOG.md +0 -7
  137. package/dist/scaffolds/notis-affiliate-prospects/README.md +0 -20
  138. package/dist/scaffolds/notis-affiliate-prospects/app/globals.css +0 -42
  139. package/dist/scaffolds/notis-affiliate-prospects/app/layout.tsx +0 -7
  140. package/dist/scaffolds/notis-affiliate-prospects/app/page.tsx +0 -248
  141. package/dist/scaffolds/notis-affiliate-prospects/app/prospects/page.tsx +0 -134
  142. package/dist/scaffolds/notis-affiliate-prospects/app/segments/page.tsx +0 -108
  143. package/dist/scaffolds/notis-affiliate-prospects/components/ui/card.tsx +0 -35
  144. package/dist/scaffolds/notis-affiliate-prospects/index.html +0 -12
  145. package/dist/scaffolds/notis-affiliate-prospects/lib/affiliate-data.ts +0 -218
  146. package/dist/scaffolds/notis-affiliate-prospects/lib/demo-prospects.ts +0 -52
  147. package/dist/scaffolds/notis-affiliate-prospects/lib/notis-tools.ts +0 -100
  148. package/dist/scaffolds/notis-affiliate-prospects/lib/utils.ts +0 -25
  149. package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-1.png +0 -0
  150. package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-2.png +0 -0
  151. package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-3.png +0 -0
  152. package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-fixtures.json +0 -187
  153. package/dist/scaffolds/notis-affiliate-prospects/notis.config.ts +0 -65
  154. package/dist/scaffolds/notis-affiliate-prospects/package.json +0 -32
  155. package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/config.ts +0 -90
  156. package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/hooks/useDatabase.ts +0 -76
  157. package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/hooks/useUpsertDocument.ts +0 -50
  158. package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/styles.css +0 -38
  159. package/dist/scaffolds/notis-affiliate-prospects/postcss.config.mjs +0 -6
  160. package/dist/scaffolds/notis-affiliate-prospects/src/dev-main.tsx +0 -72
  161. package/dist/scaffolds/notis-affiliate-prospects/src/mock-runtime.ts +0 -242
  162. package/dist/scaffolds/notis-affiliate-prospects/tailwind.config.ts +0 -51
  163. package/dist/scaffolds/notis-affiliate-prospects/tsconfig.json +0 -23
  164. package/dist/scaffolds/notis-affiliate-prospects/vite.config.ts +0 -11
  165. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -0
  166. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -0
  167. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useBackend.ts +0 -0
  168. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useNotis.ts +0 -0
  169. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useNotisNavigation.ts +0 -0
  170. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useTool.ts +0 -0
  171. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useTools.ts +0 -0
  172. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useTopBarSearch.ts +0 -0
  173. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/provider.tsx +0 -0
  174. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/ui.ts +0 -0
  175. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/vite.ts +0 -0
  176. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/tsconfig.json +0 -0
  177. /package/dist/scaffolds/{notis-affiliate-prospects → notis-random}/components.json +0 -0
@@ -1,248 +0,0 @@
1
- 'use client';
2
-
3
- import { useEffect, useMemo, useState } from 'react';
4
- import { useNotisNavigation, useTopBarSearch } from '@notis/sdk';
5
- import {
6
- ArrowClockwiseIcon,
7
- ArrowRightIcon,
8
- CheckCircleIcon,
9
- CurrencyDollarIcon,
10
- FunnelIcon,
11
- PaperPlaneTiltIcon,
12
- SparkleIcon,
13
- UsersThreeIcon,
14
- } from '@phosphor-icons/react';
15
-
16
- import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
17
- import {
18
- PIPELINE,
19
- SEGMENTS,
20
- compactNumber,
21
- money,
22
- statusTone,
23
- type AffiliateProspect,
24
- } from '@/lib/affiliate-data';
25
- import { useAffiliateProspects } from '@/lib/notis-tools';
26
-
27
- export default function DashboardPage() {
28
- const { prospects, loading, error, refetch } = useAffiliateProspects();
29
- const navigation = useNotisNavigation();
30
- const [search, setSearch] = useState('');
31
- const { setLoading } = useTopBarSearch({
32
- value: search,
33
- onChange: setSearch,
34
- placeholder: 'Filter affiliate prospects…',
35
- });
36
- useEffect(() => setLoading(loading), [loading, setLoading]);
37
-
38
- const filtered = useMemo(() => {
39
- const query = search.trim().toLowerCase();
40
- if (!query) return prospects;
41
- return prospects.filter((prospect) =>
42
- [prospect.name, prospect.company, prospect.segment, prospect.status, prospect.source]
43
- .join(' ')
44
- .toLowerCase()
45
- .includes(query),
46
- );
47
- }, [prospects, search]);
48
-
49
- const metrics = useMemo(() => {
50
- const qualified = prospects.filter((p) => p.fitScore >= 75 && p.status !== 'Disqualified').length;
51
- const active = prospects.filter((p) => p.status === 'Activated').length;
52
- const conversations = prospects.filter((p) =>
53
- ['Replied', 'Interested', 'Applied', 'Activated'].includes(p.status),
54
- ).length;
55
- const mrr = prospects.reduce((sum, p) => sum + p.attributedMrr, 0);
56
- return { qualified, active, conversations, mrr };
57
- }, [prospects]);
58
-
59
- const priority = useMemo(
60
- () =>
61
- [...filtered]
62
- .filter((p) => !p.optedOut && !['Activated', 'Disqualified'].includes(p.status))
63
- .sort((a, b) => b.fitScore - a.fitScore)
64
- .slice(0, 6),
65
- [filtered],
66
- );
67
-
68
- const segmentRows = useMemo(
69
- () =>
70
- SEGMENTS.map((segment) => {
71
- const rows = prospects.filter((p) => p.segment === segment.name);
72
- const engaged = rows.filter((p) => ['Replied', 'Interested', 'Applied', 'Activated'].includes(p.status)).length;
73
- return { ...segment, total: rows.length, engaged };
74
- }).sort((a, b) => b.total - a.total),
75
- [prospects],
76
- );
77
-
78
- return (
79
- <main data-store-screenshot="dashboard" className="affiliate-app-shell space-y-6">
80
- <header className="flex flex-wrap items-start justify-between gap-4">
81
- <div>
82
- <div className="flex items-center gap-2 text-xs font-medium text-muted-foreground">
83
- <SparkleIcon size={14} weight="fill" />
84
- Affiliate recruiting control center
85
- </div>
86
- <h1 className="mt-1 text-2xl font-semibold tracking-tight">Campaign dashboard</h1>
87
- <p className="mt-1 max-w-2xl text-sm text-muted-foreground">
88
- One place to source, qualify, contact, and activate affiliates—while Notis watches the
89
- pipeline and local Codex workers do the heavy research.
90
- </p>
91
- </div>
92
- <button
93
- type="button"
94
- onClick={refetch}
95
- className="inline-flex min-h-11 items-center gap-2 rounded-md border border-border bg-background px-3 text-sm text-muted-foreground transition hover:bg-muted hover:text-foreground"
96
- >
97
- <ArrowClockwiseIcon className={loading ? 'animate-spin' : ''} size={16} />
98
- Refresh
99
- </button>
100
- </header>
101
-
102
- {error ? (
103
- <div className="rounded-lg border border-destructive/20 bg-destructive/10 px-4 py-3 text-sm text-destructive">
104
- {error.message}
105
- </div>
106
- ) : null}
107
-
108
- <section className="affiliate-stat-grid gap-3">
109
- <Metric icon={UsersThreeIcon} label="Prospects" value={compactNumber(prospects.length)} />
110
- <Metric icon={FunnelIcon} label="Qualified" value={compactNumber(metrics.qualified)} />
111
- <Metric icon={PaperPlaneTiltIcon} label="Conversations" value={compactNumber(metrics.conversations)} />
112
- <Metric icon={CheckCircleIcon} label="Activated" value={compactNumber(metrics.active)} />
113
- <Metric icon={CurrencyDollarIcon} label="Attributed MRR" value={money(metrics.mrr)} />
114
- </section>
115
-
116
- <section className="affiliate-dashboard-grid gap-4">
117
- <Card>
118
- <CardHeader className="flex-row items-center justify-between">
119
- <div>
120
- <CardTitle>Priority queue</CardTitle>
121
- <p className="mt-1 text-xs text-muted-foreground">Highest-fit prospects needing a next action.</p>
122
- </div>
123
- <button
124
- type="button"
125
- onClick={() => navigation.toRoute('/prospects')}
126
- className="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground"
127
- >
128
- All prospects <ArrowRightIcon size={13} />
129
- </button>
130
- </CardHeader>
131
- <CardContent>
132
- {loading && !prospects.length ? (
133
- <p className="py-8 text-center text-sm text-muted-foreground">Loading the pipeline…</p>
134
- ) : priority.length ? (
135
- <div className="divide-y divide-border">
136
- {priority.map((prospect) => <PriorityRow key={prospect.id} prospect={prospect} />)}
137
- </div>
138
- ) : (
139
- <p className="rounded-lg border border-dashed border-border py-8 text-center text-sm text-muted-foreground">
140
- No prospects need attention.
141
- </p>
142
- )}
143
- </CardContent>
144
- </Card>
145
-
146
- <Card>
147
- <CardHeader>
148
- <CardTitle>Pipeline</CardTitle>
149
- <p className="text-xs text-muted-foreground">Current distribution across recruiting stages.</p>
150
- </CardHeader>
151
- <CardContent className="space-y-3">
152
- {PIPELINE.slice(0, 9).map((status) => {
153
- const count = prospects.filter((p) => p.status === status).length;
154
- const width = prospects.length ? Math.max((count / prospects.length) * 100, count ? 4 : 0) : 0;
155
- return (
156
- <div key={status}>
157
- <div className="mb-1 flex items-center justify-between text-xs">
158
- <span className="text-muted-foreground">{status}</span>
159
- <span className="font-medium tabular-nums">{count}</span>
160
- </div>
161
- <div className="h-1.5 overflow-hidden rounded-full bg-muted">
162
- <div className="h-full rounded-full bg-primary/70" style={{ width: `${width}%` }} />
163
- </div>
164
- </div>
165
- );
166
- })}
167
- </CardContent>
168
- </Card>
169
- </section>
170
-
171
- <Card>
172
- <CardHeader className="flex-row items-center justify-between">
173
- <div>
174
- <CardTitle>Segment coverage</CardTitle>
175
- <p className="mt-1 text-xs text-muted-foreground">Where the campaign is deep—and where sourcing should run next.</p>
176
- </div>
177
- <button
178
- type="button"
179
- onClick={() => navigation.toRoute('/segments')}
180
- className="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground"
181
- >
182
- Playbooks <ArrowRightIcon size={13} />
183
- </button>
184
- </CardHeader>
185
- <CardContent>
186
- <div className="grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-4">
187
- {segmentRows.map((segment) => (
188
- <div key={segment.name} className="rounded-lg border border-border p-3">
189
- <p className="text-sm font-medium">{segment.short}</p>
190
- <p className="mt-1 text-xs text-muted-foreground">{segment.source}</p>
191
- <div className="mt-3 flex items-end justify-between">
192
- <span className="text-2xl font-semibold tabular-nums">{segment.total}</span>
193
- <span className="text-xs text-muted-foreground">{segment.engaged} engaged</span>
194
- </div>
195
- </div>
196
- ))}
197
- </div>
198
- </CardContent>
199
- </Card>
200
- </main>
201
- );
202
- }
203
-
204
- function Metric({
205
- icon: Icon,
206
- label,
207
- value,
208
- }: {
209
- icon: typeof UsersThreeIcon;
210
- label: string;
211
- value: string;
212
- }) {
213
- return (
214
- <Card className="p-4">
215
- <div className="flex items-center justify-between gap-3">
216
- <div>
217
- <p className="text-xs text-muted-foreground">{label}</p>
218
- <p className="mt-1 text-2xl font-semibold tabular-nums">{value}</p>
219
- </div>
220
- <div className="rounded-lg bg-muted p-2 text-muted-foreground">
221
- <Icon size={18} />
222
- </div>
223
- </div>
224
- </Card>
225
- );
226
- }
227
-
228
- function PriorityRow({ prospect }: { prospect: AffiliateProspect }) {
229
- return (
230
- <div className="flex flex-wrap items-center gap-3 py-3 first:pt-0 last:pb-0">
231
- <div className="flex min-w-0 flex-1 items-center gap-3">
232
- <div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-muted text-xs font-semibold">
233
- {prospect.name.split(/\s+/).slice(0, 2).map((part) => part[0]).join('')}
234
- </div>
235
- <div className="min-w-0">
236
- <p className="truncate text-sm font-medium">{prospect.name}</p>
237
- <p className="truncate text-xs text-muted-foreground">
238
- {prospect.company || prospect.segment} · {prospect.nextAction || 'Review qualification'}
239
- </p>
240
- </div>
241
- </div>
242
- <span className={`rounded-full px-2 py-1 text-[11px] font-medium ${statusTone(prospect.status)}`}>
243
- {prospect.status}
244
- </span>
245
- <span className="w-12 text-right text-sm font-semibold tabular-nums">{prospect.fitScore}</span>
246
- </div>
247
- );
248
- }
@@ -1,134 +0,0 @@
1
- 'use client';
2
-
3
- import { useEffect, useMemo, useState } from 'react';
4
- import { useTopBarSearch } from '@notis/sdk';
5
- import { ArrowClockwiseIcon, CalendarBlankIcon, EnvelopeSimpleIcon, LinkedinLogoIcon } from '@phosphor-icons/react';
6
-
7
- import { Card } from '@/components/ui/card';
8
- import { compactNumber, statusTone } from '@/lib/affiliate-data';
9
- import { useAffiliateProspects } from '@/lib/notis-tools';
10
-
11
- export default function ProspectsPage() {
12
- const { prospects, loading, error, refetch } = useAffiliateProspects();
13
- const [search, setSearch] = useState('');
14
- const { setLoading } = useTopBarSearch({
15
- value: search,
16
- onChange: setSearch,
17
- placeholder: 'Search name, company, segment, status, or source…',
18
- });
19
- useEffect(() => setLoading(loading), [loading, setLoading]);
20
-
21
- const rows = useMemo(() => {
22
- const query = search.trim().toLowerCase();
23
- return [...prospects]
24
- .filter((prospect) =>
25
- !query ||
26
- [prospect.name, prospect.company, prospect.segment, prospect.status, prospect.source]
27
- .join(' ')
28
- .toLowerCase()
29
- .includes(query),
30
- )
31
- .sort((a, b) => b.fitScore - a.fitScore);
32
- }, [prospects, search]);
33
-
34
- return (
35
- <main data-store-screenshot="prospects" className="affiliate-app-shell space-y-5">
36
- <header className="flex flex-wrap items-start justify-between gap-4">
37
- <div>
38
- <p className="text-xs font-medium text-muted-foreground">Affiliate recruiting CRM</p>
39
- <h1 className="mt-1 text-2xl font-semibold tracking-tight">{rows.length} prospects</h1>
40
- <p className="mt-1 text-sm text-muted-foreground">
41
- Ask Notis to add, enrich, qualify, or move prospects; this view stays the reference layer.
42
- </p>
43
- </div>
44
- <button
45
- type="button"
46
- onClick={refetch}
47
- className="inline-flex min-h-11 items-center gap-2 rounded-md border border-border px-3 text-sm text-muted-foreground hover:bg-muted hover:text-foreground"
48
- >
49
- <ArrowClockwiseIcon className={loading ? 'animate-spin' : ''} size={16} />
50
- Refresh
51
- </button>
52
- </header>
53
-
54
- {error ? <p className="rounded-lg bg-destructive/10 px-4 py-3 text-sm text-destructive">{error.message}</p> : null}
55
-
56
- <Card className="affiliate-desktop-table overflow-x-auto">
57
- <table className="affiliate-data-table w-full border-collapse text-left">
58
- <thead className="border-b border-border bg-muted/40 text-[11px] uppercase tracking-wide text-muted-foreground">
59
- <tr>
60
- <th className="px-4 py-3 font-medium">Prospect</th>
61
- <th className="px-4 py-3 font-medium">Segment</th>
62
- <th className="px-4 py-3 font-medium">Status</th>
63
- <th className="px-4 py-3 text-right font-medium">Fit</th>
64
- <th className="px-4 py-3 font-medium">Source</th>
65
- <th className="px-4 py-3 font-medium">Next action</th>
66
- <th className="px-4 py-3 font-medium">Reach</th>
67
- </tr>
68
- </thead>
69
- <tbody className="divide-y divide-border">
70
- {rows.map((prospect) => (
71
- <tr key={prospect.id} className="transition hover:bg-muted/30">
72
- <td className="px-4 py-3">
73
- <p className="text-sm font-medium">{prospect.name}</p>
74
- <p className="text-xs text-muted-foreground">{prospect.company || prospect.website}</p>
75
- </td>
76
- <td className="px-4 py-3 text-xs text-muted-foreground">{prospect.segment}</td>
77
- <td className="px-4 py-3">
78
- <span className={`rounded-full px-2 py-1 text-[11px] font-medium ${statusTone(prospect.status)}`}>
79
- {prospect.status}
80
- </span>
81
- </td>
82
- <td className="px-4 py-3 text-right text-sm font-semibold tabular-nums">{prospect.fitScore}</td>
83
- <td className="px-4 py-3 text-xs text-muted-foreground">{prospect.source}</td>
84
- <td className="px-4 py-3">
85
- <p className="max-w-52 truncate text-xs">{prospect.nextAction || '—'}</p>
86
- <p className="text-[11px] text-muted-foreground">{formatDate(prospect.nextActionDate)}</p>
87
- </td>
88
- <td className="px-4 py-3">
89
- <div className="flex items-center gap-2 text-muted-foreground">
90
- {prospect.email ? <EnvelopeSimpleIcon size={15} /> : null}
91
- {prospect.linkedInUrl ? <LinkedinLogoIcon size={15} /> : null}
92
- <span className="text-[11px]">{compactNumber(prospect.audienceSize || prospect.trafficEstimate)}</span>
93
- </div>
94
- </td>
95
- </tr>
96
- ))}
97
- </tbody>
98
- </table>
99
- </Card>
100
-
101
- <div className="affiliate-mobile-cards space-y-3">
102
- {rows.map((prospect) => (
103
- <Card key={prospect.id} className="p-4">
104
- <div className="flex items-start justify-between gap-3">
105
- <div className="min-w-0">
106
- <p className="truncate text-sm font-medium">{prospect.name}</p>
107
- <p className="truncate text-xs text-muted-foreground">{prospect.company || prospect.segment}</p>
108
- </div>
109
- <span className="text-sm font-semibold tabular-nums">{prospect.fitScore}</span>
110
- </div>
111
- <div className="mt-3 flex flex-wrap items-center gap-2">
112
- <span className={`rounded-full px-2 py-1 text-[11px] font-medium ${statusTone(prospect.status)}`}>
113
- {prospect.status}
114
- </span>
115
- <span className="rounded-full bg-muted px-2 py-1 text-[11px] text-muted-foreground">{prospect.segment}</span>
116
- </div>
117
- <div className="mt-3 flex items-center gap-2 text-xs text-muted-foreground">
118
- <CalendarBlankIcon size={14} />
119
- {prospect.nextAction || 'Review qualification'} · {formatDate(prospect.nextActionDate)}
120
- </div>
121
- </Card>
122
- ))}
123
- </div>
124
- </main>
125
- );
126
- }
127
-
128
- function formatDate(value: string): string {
129
- if (!value) return '';
130
- const date = new Date(value);
131
- return Number.isNaN(date.getTime())
132
- ? value
133
- : new Intl.DateTimeFormat('en', { month: 'short', day: 'numeric', timeZone: 'UTC' }).format(date);
134
- }
@@ -1,108 +0,0 @@
1
- 'use client';
2
-
3
- import { useMemo } from 'react';
4
- import {
5
- ArrowSquareOutIcon,
6
- ChartLineUpIcon,
7
- CheckCircleIcon,
8
- MagnifyingGlassIcon,
9
- UsersIcon,
10
- } from '@phosphor-icons/react';
11
-
12
- import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
13
- import { SEGMENTS, compactNumber } from '@/lib/affiliate-data';
14
- import { useAffiliateProspects } from '@/lib/notis-tools';
15
-
16
- export default function SegmentsPage() {
17
- const { prospects, loading, error } = useAffiliateProspects();
18
- const segments = useMemo(
19
- () =>
20
- SEGMENTS.map((segment) => {
21
- const rows = prospects.filter((prospect) => prospect.segment === segment.name);
22
- const qualified = rows.filter((prospect) => prospect.fitScore >= 75 && prospect.status !== 'Disqualified').length;
23
- const activated = rows.filter((prospect) => prospect.status === 'Activated').length;
24
- const reach = rows.reduce((sum, prospect) => sum + prospect.audienceSize + prospect.trafficEstimate, 0);
25
- return { ...segment, total: rows.length, qualified, activated, reach };
26
- }),
27
- [prospects],
28
- );
29
-
30
- return (
31
- <main data-store-screenshot="segments" className="affiliate-app-shell space-y-6">
32
- <header>
33
- <p className="text-xs font-medium text-muted-foreground">Seven focused acquisition motions</p>
34
- <h1 className="mt-1 text-2xl font-semibold tracking-tight">Recruiting segments</h1>
35
- <p className="mt-1 max-w-2xl text-sm text-muted-foreground">
36
- Each segment has its own sourcing signal, qualification model, and outreach playbook in
37
- <span className="font-medium text-foreground"> campaign/affiliate-network/segments</span>.
38
- </p>
39
- </header>
40
-
41
- {error ? (
42
- <p className="rounded-lg bg-destructive/10 px-4 py-3 text-sm text-destructive">{error.message}</p>
43
- ) : null}
44
- {loading && !prospects.length ? (
45
- <p className="rounded-lg border border-dashed border-border py-8 text-center text-sm text-muted-foreground">
46
- Loading segment coverage…
47
- </p>
48
- ) : null}
49
-
50
- <section className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3">
51
- {segments.map((segment, index) => (
52
- <Card key={segment.name}>
53
- <CardHeader>
54
- <div className="flex items-start justify-between gap-3">
55
- <div>
56
- <p className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
57
- Segment {index + 1}
58
- </p>
59
- <CardTitle className="mt-1 text-base">{segment.name}</CardTitle>
60
- </div>
61
- <div className="rounded-lg bg-muted p-2 text-muted-foreground">
62
- <UsersIcon size={18} />
63
- </div>
64
- </div>
65
- <p className="text-xs leading-relaxed text-muted-foreground">{segment.description}</p>
66
- </CardHeader>
67
- <CardContent>
68
- <div className="grid grid-cols-3 gap-2">
69
- <Kpi icon={MagnifyingGlassIcon} value={segment.total} label="Found" />
70
- <Kpi icon={ChartLineUpIcon} value={segment.qualified} label="Qualified" />
71
- <Kpi icon={CheckCircleIcon} value={segment.activated} label="Active" />
72
- </div>
73
- <div className="mt-4 rounded-lg bg-muted/50 p-3">
74
- <p className="text-[11px] uppercase tracking-wide text-muted-foreground">Primary source</p>
75
- <p className="mt-1 text-sm font-medium">{segment.source}</p>
76
- <p className="mt-2 text-xs text-muted-foreground">
77
- Combined discoverable reach: {compactNumber(segment.reach)}
78
- </p>
79
- </div>
80
- <div className="mt-4 flex items-center justify-between text-xs text-muted-foreground">
81
- <span>Playbook is source-controlled</span>
82
- <ArrowSquareOutIcon size={14} />
83
- </div>
84
- </CardContent>
85
- </Card>
86
- ))}
87
- </section>
88
- </main>
89
- );
90
- }
91
-
92
- function Kpi({
93
- icon: Icon,
94
- value,
95
- label,
96
- }: {
97
- icon: typeof UsersIcon;
98
- value: number;
99
- label: string;
100
- }) {
101
- return (
102
- <div className="rounded-lg border border-border p-2.5">
103
- <Icon size={14} className="text-muted-foreground" />
104
- <p className="mt-2 text-lg font-semibold tabular-nums">{value}</p>
105
- <p className="text-[10px] text-muted-foreground">{label}</p>
106
- </div>
107
- );
108
- }
@@ -1,35 +0,0 @@
1
- import * as React from 'react';
2
-
3
- import { cn } from '@/lib/utils';
4
-
5
- export const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
6
- ({ className, ...props }, ref) => (
7
- <div
8
- ref={ref}
9
- className={cn('rounded-xl border border-border bg-card text-card-foreground shadow-sm', className)}
10
- {...props}
11
- />
12
- ),
13
- );
14
- Card.displayName = 'Card';
15
-
16
- export const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
17
- ({ className, ...props }, ref) => (
18
- <div ref={ref} className={cn('flex flex-col gap-1.5 p-5', className)} {...props} />
19
- ),
20
- );
21
- CardHeader.displayName = 'CardHeader';
22
-
23
- export const CardTitle = React.forwardRef<HTMLHeadingElement, React.HTMLAttributes<HTMLHeadingElement>>(
24
- ({ className, ...props }, ref) => (
25
- <h3 ref={ref} className={cn('text-sm font-semibold tracking-tight', className)} {...props} />
26
- ),
27
- );
28
- CardTitle.displayName = 'CardTitle';
29
-
30
- export const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
31
- ({ className, ...props }, ref) => (
32
- <div ref={ref} className={cn('p-5 pt-0', className)} {...props} />
33
- ),
34
- );
35
- CardContent.displayName = 'CardContent';
@@ -1,12 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Affiliate Prospects (dev preview)</title>
7
- </head>
8
- <body>
9
- <div id="root"></div>
10
- <script type="module" src="/src/dev-main.tsx"></script>
11
- </body>
12
- </html>