create-feedbax 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +28 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +173 -0
- package/dist/index.js.map +1 -0
- package/package.json +24 -0
- package/template/.env.example +4 -0
- package/template/README.md +7 -0
- package/template/apps/portal/components.json +20 -0
- package/template/apps/portal/package.json +31 -0
- package/template/apps/portal/src/lib/utils.ts +6 -0
- package/template/apps/portal/src/routeTree.gen.ts +59 -0
- package/template/apps/portal/src/router.tsx +10 -0
- package/template/apps/portal/src/routes/__root.tsx +31 -0
- package/template/apps/portal/src/routes/index.tsx +35 -0
- package/template/apps/portal/src/styles.css +91 -0
- package/template/apps/portal/tsconfig.json +15 -0
- package/template/apps/portal/vite.config.ts +13 -0
- package/template/feedbax.config.ts +13 -0
- package/template/feedbax.jsonc +11 -0
- package/template/package.json +20 -0
- package/template/pnpm-workspace.yaml +3 -0
- package/template/turbo.json +8 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Feedbax
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createInterface } from 'node:readline/promises';
|
|
3
|
+
import { createProject, resolveCreateOptions, resolveInteractiveCreateOptions, } from './index.js';
|
|
4
|
+
try {
|
|
5
|
+
const argv = process.argv.slice(2);
|
|
6
|
+
const interactive = process.stdin.isTTY && process.stdout.isTTY;
|
|
7
|
+
const options = interactive
|
|
8
|
+
? await (async () => {
|
|
9
|
+
const terminal = createInterface({
|
|
10
|
+
input: process.stdin,
|
|
11
|
+
output: process.stdout,
|
|
12
|
+
});
|
|
13
|
+
try {
|
|
14
|
+
return await resolveInteractiveCreateOptions(argv, async (question, defaultValue) => terminal.question(`${question} [${defaultValue}]: `));
|
|
15
|
+
}
|
|
16
|
+
finally {
|
|
17
|
+
terminal.close();
|
|
18
|
+
}
|
|
19
|
+
})()
|
|
20
|
+
: resolveCreateOptions(argv);
|
|
21
|
+
const result = await createProject(options);
|
|
22
|
+
process.stdout.write(`Created Feedbax in ${result.destination}\n\n${result.nextSteps.join('\n')}\n`);
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
26
|
+
process.exitCode = 1;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,+BAA+B,GAChC,MAAM,YAAY,CAAA;AAEnB,IAAI,CAAC;IACH,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IAClC,MAAM,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,CAAA;IAC/D,MAAM,OAAO,GAAG,WAAW;QACzB,CAAC,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE;YAChB,MAAM,QAAQ,GAAG,eAAe,CAAC;gBAC/B,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,MAAM,EAAE,OAAO,CAAC,MAAM;aACvB,CAAC,CAAA;YACF,IAAI,CAAC;gBACH,OAAO,MAAM,+BAA+B,CAC1C,IAAI,EACJ,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE,CAC/B,QAAQ,CAAC,QAAQ,CAAC,GAAG,QAAQ,KAAK,YAAY,KAAK,CAAC,CACvD,CAAA;YACH,CAAC;oBAAS,CAAC;gBACT,QAAQ,CAAC,KAAK,EAAE,CAAA;YAClB,CAAC;QACH,CAAC,CAAC,EAAE;QACN,CAAC,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAA;IAC9B,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,OAAO,CAAC,CAAA;IAC3C,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,sBAAsB,MAAM,CAAC,WAAW,OAAO,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAC/E,CAAA;AACH,CAAC;AAAC,OAAO,KAAK,EAAE,CAAC;IACf,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAC9D,CAAA;IACD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAA;AACtB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
type IdentityMode = 'anonymous' | 'email' | 'handoff';
|
|
2
|
+
export type Deployment = 'cloudflare' | 'vercel' | 'node';
|
|
3
|
+
export type PackageManager = 'npm' | 'pnpm';
|
|
4
|
+
export interface CreateOptions {
|
|
5
|
+
readonly directory: string;
|
|
6
|
+
readonly identity: IdentityMode;
|
|
7
|
+
readonly deploy: Deployment;
|
|
8
|
+
readonly packageManager: PackageManager;
|
|
9
|
+
readonly preset: string;
|
|
10
|
+
readonly install: boolean;
|
|
11
|
+
readonly git: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare function resolveCreateOptions(argv: readonly string[]): CreateOptions;
|
|
14
|
+
export type CreatePrompt = (question: string, defaultValue: string) => Promise<string>;
|
|
15
|
+
export declare function resolveInteractiveCreateOptions(argv: readonly string[], prompt: CreatePrompt): Promise<CreateOptions>;
|
|
16
|
+
export declare function createProject(options: CreateOptions): Promise<{
|
|
17
|
+
destination: string;
|
|
18
|
+
nextSteps: string[];
|
|
19
|
+
}>;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,KAAK,YAAY,GAAG,WAAW,GAAG,OAAO,GAAG,SAAS,CAAA;AAErD,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,QAAQ,GAAG,MAAM,CAAA;AACzD,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,MAAM,CAAA;AAC3C,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAA;IAC/B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;IAC3B,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAA;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAA;CACtB;AAoBD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,aAAa,CA6C3E;AAED,MAAM,MAAM,YAAY,GAAG,CACzB,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,KACjB,OAAO,CAAC,MAAM,CAAC,CAAA;AAEpB,wBAAsB,+BAA+B,CACnD,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,aAAa,CAAC,CAaxB;AAyCD,wBAAsB,aAAa,CAAC,OAAO,EAAE,aAAa;;;GA8DzD"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { cp, mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { dirname, join, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
const valueFlags = new Set([
|
|
6
|
+
'--connector',
|
|
7
|
+
'--identity',
|
|
8
|
+
'--storage',
|
|
9
|
+
'--deploy',
|
|
10
|
+
'--preset',
|
|
11
|
+
'--package-manager',
|
|
12
|
+
]);
|
|
13
|
+
const booleanFlags = new Set(['--yes', '--no-install', '--no-git']);
|
|
14
|
+
const allowedDeployments = new Set(['cloudflare', 'vercel', 'node']);
|
|
15
|
+
const allowedPackageManagers = new Set(['npm', 'pnpm']);
|
|
16
|
+
const identityMode = (mode) => {
|
|
17
|
+
if (mode === 'anonymous' || mode === 'email' || mode === 'handoff')
|
|
18
|
+
return mode;
|
|
19
|
+
throw new Error(`Identity mode "${mode}" is not supported in Feedbax 0.1.0.`);
|
|
20
|
+
};
|
|
21
|
+
export function resolveCreateOptions(argv) {
|
|
22
|
+
const values = new Map();
|
|
23
|
+
let directory;
|
|
24
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
25
|
+
const entry = argv[index];
|
|
26
|
+
if (valueFlags.has(entry)) {
|
|
27
|
+
const next = argv[index + 1];
|
|
28
|
+
if (!next || next.startsWith('-'))
|
|
29
|
+
throw new Error(`Option ${entry} requires a value.`);
|
|
30
|
+
values.set(entry, next);
|
|
31
|
+
index += 1;
|
|
32
|
+
}
|
|
33
|
+
else if (booleanFlags.has(entry))
|
|
34
|
+
continue;
|
|
35
|
+
else if (entry.startsWith('-'))
|
|
36
|
+
throw new Error(`Unknown option ${entry}.`);
|
|
37
|
+
else if (directory)
|
|
38
|
+
throw new Error(`Unexpected argument ${entry}.`);
|
|
39
|
+
else
|
|
40
|
+
directory = entry;
|
|
41
|
+
}
|
|
42
|
+
const value = (name) => values.get(name);
|
|
43
|
+
directory ??= 'my-feedback';
|
|
44
|
+
const connector = value('--connector') ?? 'notion';
|
|
45
|
+
const storage = value('--storage') ?? 'notion';
|
|
46
|
+
if (connector !== 'notion')
|
|
47
|
+
throw new Error(`Connector "${connector}" is not supported in Feedbax 0.1.0.`);
|
|
48
|
+
if (storage !== 'notion')
|
|
49
|
+
throw new Error(`Storage "${storage}" is deferred until after Feedbax 0.1.0.`);
|
|
50
|
+
const identity = identityMode(value('--identity') ?? 'email');
|
|
51
|
+
const deploy = (value('--deploy') ?? 'cloudflare');
|
|
52
|
+
if (!allowedDeployments.has(deploy))
|
|
53
|
+
throw new Error(`Deployment "${deploy}" is not supported.`);
|
|
54
|
+
const packageManager = (value('--package-manager') ??
|
|
55
|
+
'pnpm');
|
|
56
|
+
if (!allowedPackageManagers.has(packageManager))
|
|
57
|
+
throw new Error(`Package manager "${packageManager}" is not supported.`);
|
|
58
|
+
return {
|
|
59
|
+
directory,
|
|
60
|
+
identity,
|
|
61
|
+
deploy,
|
|
62
|
+
packageManager,
|
|
63
|
+
preset: value('--preset') ?? 'feedbax-default',
|
|
64
|
+
install: !argv.includes('--no-install'),
|
|
65
|
+
git: !argv.includes('--no-git'),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
export async function resolveInteractiveCreateOptions(argv, prompt) {
|
|
69
|
+
if (argv.includes('--yes'))
|
|
70
|
+
return resolveCreateOptions(argv);
|
|
71
|
+
const prompted = [...argv];
|
|
72
|
+
const ask = async (flag, question, defaultValue) => {
|
|
73
|
+
if (argv.includes(flag))
|
|
74
|
+
return;
|
|
75
|
+
const answer = (await prompt(question, defaultValue)).trim() || defaultValue;
|
|
76
|
+
prompted.push(flag, answer);
|
|
77
|
+
};
|
|
78
|
+
await ask('--identity', 'Identity (anonymous, email, handoff)', 'email');
|
|
79
|
+
await ask('--deploy', 'Deployment (cloudflare, vercel, node)', 'cloudflare');
|
|
80
|
+
await ask('--package-manager', 'Package manager (pnpm, npm)', 'pnpm');
|
|
81
|
+
await ask('--preset', 'shadcn preset', 'feedbax-default');
|
|
82
|
+
return resolveCreateOptions(prompted);
|
|
83
|
+
}
|
|
84
|
+
async function assertEmpty(path) {
|
|
85
|
+
try {
|
|
86
|
+
if ((await readdir(path)).length > 0)
|
|
87
|
+
throw new Error(`Target directory is not empty: ${path}`);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
if (error.code !== 'ENOENT')
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async function replaceInTree(path, replacements) {
|
|
95
|
+
for (const entry of await readdir(path)) {
|
|
96
|
+
const target = join(path, entry);
|
|
97
|
+
if ((await stat(target)).isDirectory())
|
|
98
|
+
await replaceInTree(target, replacements);
|
|
99
|
+
else {
|
|
100
|
+
let content = await readFile(target, 'utf8');
|
|
101
|
+
for (const [token, replacement] of Object.entries(replacements))
|
|
102
|
+
content = content.replaceAll(token, replacement);
|
|
103
|
+
await writeFile(target, content);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const run = (command, args, cwd) => new Promise((resolvePromise, reject) => {
|
|
108
|
+
const child = spawn(command, args, { cwd, stdio: 'inherit', shell: false });
|
|
109
|
+
child.once('error', reject);
|
|
110
|
+
child.once('exit', (code) => code === 0
|
|
111
|
+
? resolvePromise()
|
|
112
|
+
: reject(new Error(`${command} ${args.join(' ')} failed with ${code}.`)));
|
|
113
|
+
});
|
|
114
|
+
export async function createProject(options) {
|
|
115
|
+
const destination = resolve(options.directory);
|
|
116
|
+
await assertEmpty(destination);
|
|
117
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
118
|
+
const packageDirectory = dirname(fileURLToPath(import.meta.url));
|
|
119
|
+
const candidates = [
|
|
120
|
+
resolve(packageDirectory, '../template'),
|
|
121
|
+
resolve(packageDirectory, '../../../templates/default'),
|
|
122
|
+
];
|
|
123
|
+
let template;
|
|
124
|
+
for (const candidate of candidates) {
|
|
125
|
+
try {
|
|
126
|
+
if ((await stat(candidate)).isDirectory()) {
|
|
127
|
+
template = candidate;
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (!template)
|
|
136
|
+
throw new Error('The Feedbax project template is missing from this package.');
|
|
137
|
+
await cp(template, destination, { recursive: true });
|
|
138
|
+
await replaceInTree(destination, {
|
|
139
|
+
__GENERATOR_VERSION__: '0.1.0',
|
|
140
|
+
__IDENTITY__: options.identity,
|
|
141
|
+
__DEPLOYMENT__: options.deploy,
|
|
142
|
+
__PACKAGE_MANAGER__: options.packageManager,
|
|
143
|
+
__PACKAGE_MANAGER_SPEC__: options.packageManager === 'pnpm' ? 'pnpm@10.29.3' : 'npm@11.4.2',
|
|
144
|
+
__PRESET__: options.preset,
|
|
145
|
+
__NITRO_PRESET__: options.deploy === 'vercel'
|
|
146
|
+
? 'vercel'
|
|
147
|
+
: options.deploy === 'cloudflare'
|
|
148
|
+
? 'cloudflare-module'
|
|
149
|
+
: 'node-server',
|
|
150
|
+
});
|
|
151
|
+
try {
|
|
152
|
+
if (options.install) {
|
|
153
|
+
await run(options.packageManager, ['install'], destination);
|
|
154
|
+
await run(options.packageManager, ['run', 'type-check'], destination);
|
|
155
|
+
}
|
|
156
|
+
if (options.git)
|
|
157
|
+
await run('git', ['init'], destination);
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
await writeFile(join(destination, '.feedbax-recovery'), `Generation stopped: ${error instanceof Error ? error.message : String(error)}\nRerun dependency installation and type checking after resolving the reported issue.\n`);
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
destination,
|
|
165
|
+
nextSteps: [
|
|
166
|
+
`cd ${options.directory}`,
|
|
167
|
+
'Copy .env.example to .env and configure Notion',
|
|
168
|
+
`${options.packageManager === 'pnpm' ? 'pnpm' : 'npx'} feedbax doctor`,
|
|
169
|
+
`${options.packageManager} run dev`,
|
|
170
|
+
],
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-feedbax",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"bin": {
|
|
6
|
+
"create-feedbax": "dist/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"template",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"dependencies": {},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@types/node": "24.10.13"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc -p tsconfig.json",
|
|
20
|
+
"type-check": "tsc -p tsconfig.json --noEmit",
|
|
21
|
+
"lint": "eslint src test",
|
|
22
|
+
"test": "vitest run"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Feedbax portal
|
|
2
|
+
|
|
3
|
+
1. Copy `.env.example` to `.env` and configure the server-only values.
|
|
4
|
+
2. Run `npx feedbax doctor`.
|
|
5
|
+
3. Run `pnpm dev` (or `npm run dev`).
|
|
6
|
+
|
|
7
|
+
Email identity is capture-only and does not verify ownership. Notion-backed interaction uniqueness is best effort.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "new-york",
|
|
4
|
+
"rsc": false,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "src/styles.css",
|
|
9
|
+
"baseColor": "zinc",
|
|
10
|
+
"cssVariables": true
|
|
11
|
+
},
|
|
12
|
+
"iconLibrary": "lucide",
|
|
13
|
+
"aliases": {
|
|
14
|
+
"components": "@/components",
|
|
15
|
+
"utils": "@/lib/utils",
|
|
16
|
+
"ui": "@/components/ui",
|
|
17
|
+
"lib": "@/lib",
|
|
18
|
+
"hooks": "@/hooks"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@feedbax/portal",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "vite dev --port 3000",
|
|
7
|
+
"build": "vite build",
|
|
8
|
+
"type-check": "tsc --noEmit"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@feedbax/core": "^0.1.0",
|
|
12
|
+
"@feedbax/notion": "^0.1.0",
|
|
13
|
+
"@tanstack/react-router": "1.170.18",
|
|
14
|
+
"@tanstack/react-start": "1.168.20",
|
|
15
|
+
"class-variance-authority": "0.7.1",
|
|
16
|
+
"clsx": "2.1.1",
|
|
17
|
+
"react": "^19.2.4",
|
|
18
|
+
"react-dom": "^19.2.4",
|
|
19
|
+
"tailwind-merge": "3.5.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@tailwindcss/vite": "4.1.18",
|
|
23
|
+
"@tanstack/router-plugin": "1.168.20",
|
|
24
|
+
"@types/react": "^19.2.14",
|
|
25
|
+
"@types/react-dom": "^19.2.3",
|
|
26
|
+
"@vitejs/plugin-react": "5.1.4",
|
|
27
|
+
"nitro": "3.0.260610-beta",
|
|
28
|
+
"tailwindcss": "4.1.18",
|
|
29
|
+
"vite": "7.3.6"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
// Generated by TanStack Router. It is refreshed by the Vite router plugin.
|
|
4
|
+
|
|
5
|
+
import { Route as rootRouteImport } from './routes/__root'
|
|
6
|
+
import { Route as IndexRouteImport } from './routes/index'
|
|
7
|
+
|
|
8
|
+
const IndexRoute = IndexRouteImport.update({
|
|
9
|
+
id: '/',
|
|
10
|
+
path: '/',
|
|
11
|
+
getParentRoute: () => rootRouteImport,
|
|
12
|
+
} as any)
|
|
13
|
+
|
|
14
|
+
export interface FileRoutesByFullPath {
|
|
15
|
+
'/': typeof IndexRoute
|
|
16
|
+
}
|
|
17
|
+
export interface FileRoutesByTo {
|
|
18
|
+
'/': typeof IndexRoute
|
|
19
|
+
}
|
|
20
|
+
export interface FileRoutesById {
|
|
21
|
+
__root__: typeof rootRouteImport
|
|
22
|
+
'/': typeof IndexRoute
|
|
23
|
+
}
|
|
24
|
+
export interface FileRouteTypes {
|
|
25
|
+
fileRoutesByFullPath: FileRoutesByFullPath
|
|
26
|
+
fullPaths: '/'
|
|
27
|
+
fileRoutesByTo: FileRoutesByTo
|
|
28
|
+
to: '/'
|
|
29
|
+
id: '__root__' | '/'
|
|
30
|
+
fileRoutesById: FileRoutesById
|
|
31
|
+
}
|
|
32
|
+
export interface RootRouteChildren {
|
|
33
|
+
IndexRoute: typeof IndexRoute
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
declare module '@tanstack/react-router' {
|
|
37
|
+
interface FileRoutesByPath {
|
|
38
|
+
'/': {
|
|
39
|
+
id: '/'
|
|
40
|
+
path: '/'
|
|
41
|
+
fullPath: '/'
|
|
42
|
+
preLoaderRoute: typeof IndexRouteImport
|
|
43
|
+
parentRoute: typeof rootRouteImport
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const rootRouteChildren: RootRouteChildren = { IndexRoute }
|
|
49
|
+
export const routeTree = rootRouteImport
|
|
50
|
+
._addFileChildren(rootRouteChildren)
|
|
51
|
+
._addFileTypes<FileRouteTypes>()
|
|
52
|
+
|
|
53
|
+
import type { getRouter } from './router.tsx'
|
|
54
|
+
declare module '@tanstack/react-start' {
|
|
55
|
+
interface Register {
|
|
56
|
+
ssr: true
|
|
57
|
+
router: Awaited<ReturnType<typeof getRouter>>
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createRouter } from '@tanstack/react-router'
|
|
2
|
+
import { routeTree } from './routeTree.gen'
|
|
3
|
+
export function getRouter() {
|
|
4
|
+
return createRouter({ routeTree, scrollRestoration: true })
|
|
5
|
+
}
|
|
6
|
+
declare module '@tanstack/react-router' {
|
|
7
|
+
interface Register {
|
|
8
|
+
router: ReturnType<typeof getRouter>
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createRootRoute,
|
|
3
|
+
HeadContent,
|
|
4
|
+
Outlet,
|
|
5
|
+
Scripts,
|
|
6
|
+
} from '@tanstack/react-router'
|
|
7
|
+
import styles from '../styles.css?url'
|
|
8
|
+
export const Route = createRootRoute({
|
|
9
|
+
head: () => ({
|
|
10
|
+
meta: [
|
|
11
|
+
{ charSet: 'utf-8' },
|
|
12
|
+
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
|
13
|
+
{ title: 'Feedbax' },
|
|
14
|
+
],
|
|
15
|
+
links: [{ rel: 'stylesheet', href: styles }],
|
|
16
|
+
}),
|
|
17
|
+
component: Root,
|
|
18
|
+
})
|
|
19
|
+
function Root() {
|
|
20
|
+
return (
|
|
21
|
+
<html lang="en">
|
|
22
|
+
<head>
|
|
23
|
+
<HeadContent />
|
|
24
|
+
</head>
|
|
25
|
+
<body>
|
|
26
|
+
<Outlet />
|
|
27
|
+
<Scripts />
|
|
28
|
+
</body>
|
|
29
|
+
</html>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
export const Route = createFileRoute('/')({ component: Portal })
|
|
3
|
+
function Portal() {
|
|
4
|
+
return (
|
|
5
|
+
<main>
|
|
6
|
+
<header>
|
|
7
|
+
<strong>Feedbax</strong>
|
|
8
|
+
<nav>
|
|
9
|
+
<a href="/">Feedback</a>
|
|
10
|
+
<a href="/roadmap">Roadmap</a>
|
|
11
|
+
<a href="/changelog">Changelog</a>
|
|
12
|
+
</nav>
|
|
13
|
+
</header>
|
|
14
|
+
<section>
|
|
15
|
+
<p className="eyebrow">PUBLIC FEEDBACK</p>
|
|
16
|
+
<h1>What should we build next?</h1>
|
|
17
|
+
<p>
|
|
18
|
+
Browse ideas, vote on priorities, and share feedback with the team.
|
|
19
|
+
</p>
|
|
20
|
+
<button type="button">Submit feedback</button>
|
|
21
|
+
</section>
|
|
22
|
+
<section aria-label="Feedback">
|
|
23
|
+
<div className="toolbar">
|
|
24
|
+
<input aria-label="Search feedback" placeholder="Search feedback" />
|
|
25
|
+
<select aria-label="Status">
|
|
26
|
+
<option>All statuses</option>
|
|
27
|
+
</select>
|
|
28
|
+
</div>
|
|
29
|
+
<p className="empty">
|
|
30
|
+
Connect Notion and run <code>feedbax doctor</code> to load your board.
|
|
31
|
+
</p>
|
|
32
|
+
</section>
|
|
33
|
+
</main>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
@import 'tailwindcss';
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
font-family: Inter, ui-sans-serif, system-ui, sans-serif;
|
|
5
|
+
color-scheme: light dark;
|
|
6
|
+
--background: #f6f5f1;
|
|
7
|
+
--foreground: #171815;
|
|
8
|
+
--primary: #315d42;
|
|
9
|
+
--border: #d7d7cf;
|
|
10
|
+
--radius: 0.65rem;
|
|
11
|
+
}
|
|
12
|
+
* {
|
|
13
|
+
box-sizing: border-box;
|
|
14
|
+
}
|
|
15
|
+
body {
|
|
16
|
+
margin: 0;
|
|
17
|
+
background: var(--background);
|
|
18
|
+
color: var(--foreground);
|
|
19
|
+
}
|
|
20
|
+
main {
|
|
21
|
+
max-width: 72rem;
|
|
22
|
+
margin: auto;
|
|
23
|
+
padding: 0 2rem;
|
|
24
|
+
}
|
|
25
|
+
header {
|
|
26
|
+
height: 4.5rem;
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
justify-content: space-between;
|
|
30
|
+
border-bottom: 1px solid var(--border);
|
|
31
|
+
}
|
|
32
|
+
nav {
|
|
33
|
+
display: flex;
|
|
34
|
+
gap: 1.5rem;
|
|
35
|
+
}
|
|
36
|
+
a {
|
|
37
|
+
color: inherit;
|
|
38
|
+
text-decoration: none;
|
|
39
|
+
}
|
|
40
|
+
section {
|
|
41
|
+
padding: 5rem 0;
|
|
42
|
+
border-bottom: 1px solid var(--border);
|
|
43
|
+
}
|
|
44
|
+
h1 {
|
|
45
|
+
max-width: 14ch;
|
|
46
|
+
font-size: clamp(2.8rem, 7vw, 6rem);
|
|
47
|
+
line-height: 0.95;
|
|
48
|
+
letter-spacing: -0.06em;
|
|
49
|
+
margin: 0.4rem 0 1.5rem;
|
|
50
|
+
}
|
|
51
|
+
.eyebrow {
|
|
52
|
+
font-size: 0.75rem;
|
|
53
|
+
letter-spacing: 0.18em;
|
|
54
|
+
}
|
|
55
|
+
.toolbar {
|
|
56
|
+
display: flex;
|
|
57
|
+
gap: 1rem;
|
|
58
|
+
}
|
|
59
|
+
input,
|
|
60
|
+
select,
|
|
61
|
+
button {
|
|
62
|
+
font: inherit;
|
|
63
|
+
border: 1px solid var(--border);
|
|
64
|
+
border-radius: var(--radius);
|
|
65
|
+
padding: 0.8rem 1rem;
|
|
66
|
+
background: transparent;
|
|
67
|
+
}
|
|
68
|
+
button {
|
|
69
|
+
background: var(--primary);
|
|
70
|
+
color: white;
|
|
71
|
+
border-color: var(--primary);
|
|
72
|
+
}
|
|
73
|
+
.empty {
|
|
74
|
+
padding: 4rem 0;
|
|
75
|
+
color: #666;
|
|
76
|
+
}
|
|
77
|
+
@media (max-width: 640px) {
|
|
78
|
+
main {
|
|
79
|
+
padding: 0 1.1rem;
|
|
80
|
+
}
|
|
81
|
+
nav {
|
|
82
|
+
gap: 0.75rem;
|
|
83
|
+
font-size: 0.85rem;
|
|
84
|
+
}
|
|
85
|
+
section {
|
|
86
|
+
padding: 3rem 0;
|
|
87
|
+
}
|
|
88
|
+
.toolbar {
|
|
89
|
+
flex-direction: column;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"jsx": "react-jsx",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"noEmit": true,
|
|
10
|
+
"baseUrl": ".",
|
|
11
|
+
"paths": { "@/*": ["./src/*"] },
|
|
12
|
+
"types": ["vite/client"]
|
|
13
|
+
},
|
|
14
|
+
"include": ["src", "vite.config.ts"]
|
|
15
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
|
|
2
|
+
import react from '@vitejs/plugin-react'
|
|
3
|
+
import { nitro } from 'nitro/vite'
|
|
4
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
5
|
+
import { defineConfig } from 'vite'
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
plugins: [
|
|
8
|
+
tailwindcss(),
|
|
9
|
+
tanstackStart(),
|
|
10
|
+
nitro({ preset: '__NITRO_PRESET__' }),
|
|
11
|
+
react(),
|
|
12
|
+
],
|
|
13
|
+
})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineConfig } from '@feedbax/core'
|
|
2
|
+
export default defineConfig({
|
|
3
|
+
branding: {
|
|
4
|
+
name: 'My Feedback',
|
|
5
|
+
description: 'Help us decide what to build next',
|
|
6
|
+
},
|
|
7
|
+
features: { comments: true, roadmap: true, changelog: true },
|
|
8
|
+
statuses: [
|
|
9
|
+
{ id: 'new', label: 'New' },
|
|
10
|
+
{ id: 'planned', label: 'Planned' },
|
|
11
|
+
{ id: 'shipped', label: 'Shipped', terminal: true },
|
|
12
|
+
],
|
|
13
|
+
})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://feedbax.dev/schema/project.json",
|
|
3
|
+
"version": 1,
|
|
4
|
+
"generatorVersion": "__GENERATOR_VERSION__",
|
|
5
|
+
"connector": "notion",
|
|
6
|
+
"identity": "__IDENTITY__",
|
|
7
|
+
"interactionStore": "notion",
|
|
8
|
+
"deployment": "__DEPLOYMENT__",
|
|
9
|
+
"packageManager": "__PACKAGE_MANAGER__",
|
|
10
|
+
"shadcn": { "preset": "__PRESET__", "workspace": "apps/portal" },
|
|
11
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "feedbax-project",
|
|
3
|
+
"private": true,
|
|
4
|
+
"packageManager": "__PACKAGE_MANAGER_SPEC__",
|
|
5
|
+
"workspaces": [
|
|
6
|
+
"apps/*",
|
|
7
|
+
"packages/*"
|
|
8
|
+
],
|
|
9
|
+
"scripts": {
|
|
10
|
+
"dev": "turbo run dev",
|
|
11
|
+
"build": "turbo run build",
|
|
12
|
+
"type-check": "turbo run type-check",
|
|
13
|
+
"doctor": "feedbax doctor"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"feedbax": "^0.1.0",
|
|
17
|
+
"turbo": "2.10.4",
|
|
18
|
+
"typescript": "5.9.3"
|
|
19
|
+
}
|
|
20
|
+
}
|