mirror of
https://github.com/raidendotai/cofounder
synced 2025-06-26 18:17:52 +00:00
dashboard update
This commit is contained in:
42
README.md
42
README.md
@@ -34,7 +34,7 @@ Still not merged with key target features of the project, notably :
|
||||
- code optimization
|
||||
- [...]
|
||||
|
||||
Be patient :)
|
||||
be patient :)
|
||||
|
||||
---
|
||||
|
||||
@@ -45,14 +45,14 @@ Be patient :)
|
||||
* Open your terminal and run
|
||||
|
||||
```sh
|
||||
npx @openinterface/cofounder -p "YourAppProjectName" -d "describe your app here" -a "(optional) design instructions"
|
||||
npx @openinterface/cofounder
|
||||
```
|
||||
|
||||
Follow the instructions. The installer
|
||||
- will ask you for your keys
|
||||
- setup dirs & start installs
|
||||
- will start the local `cofounder/api` builder and server
|
||||
- will start generating your app 🎉
|
||||
- will open the web dashboard where you can create new projects (at `http://localhost:667` ) 🎉
|
||||
|
||||
```
|
||||
note :
|
||||
@@ -63,9 +63,16 @@ and can be used without limits during the current early alpha period
|
||||
the full index will be available for local download on v1 release
|
||||
```
|
||||
|
||||
## Run
|
||||
```sh
|
||||
# alternatively, you can make a new project without going through the dashboard
|
||||
# by runing :
|
||||
npx @openinterface/cofounder -p "YourAppProjectName" -d "describe your app here" -a "(optional) design instructions"
|
||||
```
|
||||
|
||||
Your backend & vite+react web app will incrementally generate inside `./apps/{YourApp}`
|
||||
|
||||
## Run Generated Apps
|
||||
|
||||
- Your backend & vite+react web app will incrementally generate inside `./apps/{YourApp}`
|
||||
Open your terminal in `./apps/{YourApp}` and run
|
||||
|
||||
```sh
|
||||
@@ -75,9 +82,14 @@ npm i && npm run dev
|
||||
It will start both the backend and vite+react, concurrently, after installing their dependencies
|
||||
Go to `http://localhost:5173/` to open the web app 🎉
|
||||
|
||||
|
||||
- From within the generated apps , you can use ⌘+K / Ctrl+K to iterate on UI components
|
||||
|
||||
[more details later]
|
||||
|
||||
## Notes
|
||||
|
||||
### Local API
|
||||
### Dashboard & Local API
|
||||
|
||||
If you resume later and would like to iterate on your generated apps,
|
||||
the local `./cofounder/api` server needs to be running to receive queries
|
||||
@@ -88,12 +100,15 @@ You can (re)start the `local cofounder API` running the following command from `
|
||||
npm run start
|
||||
```
|
||||
|
||||
You can also generate new apps from the same env by running, from `./cofounder/api`, one of these command
|
||||
The dashboard will open in `http://localhost:667`
|
||||
|
||||
```sh
|
||||
npm run start -- -p "ProjectName" -f "some app description" -a "minimalist and spacious , light theme"
|
||||
npm run start -- -p "ProjectName" -f "./example_description.txt" -a "minimalist and spacious , light theme"
|
||||
```
|
||||
|
||||
- note: You can also generate new apps from the same env, without the the dashboard, by running, from `./cofounder/api`, one of these commands
|
||||
|
||||
```sh
|
||||
npm run start -- -p "ProjectName" -f "some app description" -a "minimalist and spacious , light theme"
|
||||
npm run start -- -p "ProjectName" -f "./example_description.txt" -a "minimalist and spacious , light theme"
|
||||
```
|
||||
|
||||
### Concurrency
|
||||
|
||||
@@ -128,7 +143,7 @@ nodes:
|
||||
|
||||
and change the `op:LLM::GEN` parameter `concurrency` to a higher value
|
||||
|
||||
The default LLM concurrency is set to `1` so you can see what's happening in your console streams step by step - but you can increment it to `5`-`8`
|
||||
The default LLM concurrency is set to `2` so you can see what's happening in your console streams step by step - but you can increment it depending on your api keys limits
|
||||
|
||||
---
|
||||
|
||||
@@ -155,4 +170,5 @@ archi/v1 is as follows :
|
||||
* blocks.pm by Hexa Plugin (see `cofounder/api/system/presets`)
|
||||
* google material
|
||||
* figma core
|
||||
* shadcn
|
||||
* shadcn
|
||||
- Dashboard node-based ui powered by [react flow](https://reactflow.dev/)
|
||||
@@ -29,9 +29,4 @@ DESIGNER_DESIGN_SYSTEM = "presets/shadcn" #"presets/shadcn"
|
||||
SWARM_ENABLE = TRUE
|
||||
|
||||
# OPTIONAL
|
||||
COFOUNDER_NICKNAME = "Cofounder"
|
||||
|
||||
#STATE_CLOUD = TRUE # persist on cloud (firebase + cloudstorage)
|
||||
#FIREBASE_SERVICE_KEY_PATH = ""
|
||||
#GOOGLECLOUDSTORAGE_SERVICE_KEY_PATH = ""
|
||||
#GOOGLECLOUDSTORAGE_BUCKET = ""
|
||||
COFOUNDER_NICKNAME = "Cofounder"
|
||||
@@ -1,3 +1,4 @@
|
||||
db/
|
||||
dump/
|
||||
dist/
|
||||
node_modules/
|
||||
@@ -229,32 +229,40 @@ async function build({ system }) {
|
||||
data,
|
||||
});
|
||||
const sequenceLength = sequence.length;
|
||||
if (context.sequence) {
|
||||
console.dir({ "debug:build:context:sequence": context.sequence });
|
||||
}
|
||||
const resume_at = context?.sequence?.resume ? context.sequence.resume : 0;
|
||||
let step_index = -1;
|
||||
for (const s of sequence.entries()) {
|
||||
const [index, step] = s;
|
||||
events.log.sequence.emit(`sequence:step:start`, {
|
||||
id: sequenceId,
|
||||
index,
|
||||
over: sequenceLength,
|
||||
context,
|
||||
data,
|
||||
});
|
||||
await Promise.all(
|
||||
step.map(async (parallelfnId) => {
|
||||
const response = await system.run({
|
||||
id: parallelfnId,
|
||||
context: { ...context, run: system.run },
|
||||
data,
|
||||
});
|
||||
data = merge(data, response);
|
||||
}),
|
||||
);
|
||||
events.log.sequence.emit(`sequence:step:end`, {
|
||||
id: sequenceId,
|
||||
index,
|
||||
over: sequenceLength,
|
||||
context,
|
||||
data,
|
||||
});
|
||||
step_index++;
|
||||
if (step_index >= resume_at) {
|
||||
const [index, step] = s;
|
||||
events.log.sequence.emit(`sequence:step:start`, {
|
||||
id: sequenceId,
|
||||
index,
|
||||
over: sequenceLength,
|
||||
context,
|
||||
data,
|
||||
});
|
||||
await Promise.all(
|
||||
step.map(async (parallelfnId) => {
|
||||
const response = await system.run({
|
||||
id: parallelfnId,
|
||||
context: { ...context, run: system.run },
|
||||
data,
|
||||
});
|
||||
data = merge(data, response);
|
||||
}),
|
||||
);
|
||||
events.log.sequence.emit(`sequence:step:end`, {
|
||||
id: sequenceId,
|
||||
index,
|
||||
over: sequenceLength,
|
||||
context,
|
||||
data,
|
||||
});
|
||||
}
|
||||
}
|
||||
events.log.sequence.emit(`sequence:end`, {
|
||||
id: sequenceId,
|
||||
|
||||
BIN
cofounder/api/dist/assets/cofounder-ts8-tu2p.webp
vendored
Normal file
BIN
cofounder/api/dist/assets/cofounder-ts8-tu2p.webp
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 61 KiB |
1
cofounder/api/dist/assets/index-COffgP7k.css
vendored
Normal file
1
cofounder/api/dist/assets/index-COffgP7k.css
vendored
Normal file
File diff suppressed because one or more lines are too long
370
cofounder/api/dist/assets/index-Ci0XSEHc.js
vendored
Normal file
370
cofounder/api/dist/assets/index-Ci0XSEHc.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
cofounder/api/dist/favicon.png
vendored
Normal file
BIN
cofounder/api/dist/favicon.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.7 KiB |
30
cofounder/api/dist/index.html
vendored
Normal file
30
cofounder/api/dist/index.html
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Karla:wght@100;200;300;400;500;600;700;800;900&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@100;200;300;400;500;600;700;800;900&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<title>Cofounder Dashboard</title>
|
||||
<script type="module" crossorigin src="/assets/index-Ci0XSEHc.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-COffgP7k.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script>
|
||||
document.addEventListener("keydown", (event) => {
|
||||
if (event.ctrlKey && event.key === "k") {
|
||||
console.log("Ctrl+K pressed!");
|
||||
event.preventDefault(); // Prevent doing a google search
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -4,16 +4,19 @@
|
||||
"@": "."
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node --loader esm-module-alias/loader --no-warnings server",
|
||||
"start:npx": "npm i && node --loader esm-module-alias/loader --no-warnings server",
|
||||
"dev:build": "node --loader esm-module-alias/loader --no-warnings build"
|
||||
"start:npx": "npm i && nodemon --loader esm-module-alias/loader --no-warnings server.js",
|
||||
"start": "nodemon --loader esm-module-alias/loader --no-warnings server.js"
|
||||
},
|
||||
"nodemonConfig": {
|
||||
"ignore": [
|
||||
"db/*"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.27.3",
|
||||
"@google-cloud/storage": "^7.12.1",
|
||||
"@resvg/resvg-js": "^2.6.2",
|
||||
"async-retry": "^1.3.3",
|
||||
"cloudconvert": "^2.3.7",
|
||||
"colormap": "^2.3.2",
|
||||
"cors": "^2.8.5",
|
||||
"deepmerge": "^4.3.1",
|
||||
@@ -28,12 +31,16 @@
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21",
|
||||
"module-alias": "^2.2.3",
|
||||
"open": "^10.1.0",
|
||||
"openai": "^4.55.4",
|
||||
"p-all": "^5.0.0",
|
||||
"p-queue": "^8.0.1",
|
||||
"sharp": "^0.33.4",
|
||||
"slugify": "^1.6.6",
|
||||
"socket.io": "^4.8.0",
|
||||
"vectra": "^0.9.0",
|
||||
"vite": "^5.4.8",
|
||||
"vite-express": "^0.19.0",
|
||||
"xml2js": "^0.6.2",
|
||||
"yaml": "^2.5.0",
|
||||
"yaml-js": "^0.3.1",
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
import { Server } from "socket.io";
|
||||
import utils from "@/utils/index.js";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import express from "express";
|
||||
import cors from "cors";
|
||||
import dotenv from "dotenv";
|
||||
import yargs from "yargs";
|
||||
import fs from "fs";
|
||||
import yaml from "yaml";
|
||||
import { hideBin } from "yargs/helpers";
|
||||
import { merge } from "lodash-es";
|
||||
import open, { openApp, apps } from "open";
|
||||
import cofounder from "./build.js";
|
||||
dotenv.config();
|
||||
|
||||
// -------------------------------------------------------------- HELPERS ------------------------
|
||||
function _slugify(text) {
|
||||
return text
|
||||
.toString()
|
||||
@@ -18,15 +25,17 @@ function _slugify(text) {
|
||||
.replace(/^-+/, "") // Trim - from start
|
||||
.replace(/-+$/, ""); // Trim - from end
|
||||
}
|
||||
// ----------------------------------------------------------------------------------------------------
|
||||
|
||||
// -------------------------------------------------------------- ARGS CASE ------------------------
|
||||
// init project from argv
|
||||
// to be called like : npm run start -- --p "some-project-name" --d "app description right"
|
||||
// to be called like : npm run start -- -p "some-project-name" -d "app description"
|
||||
const timestamp = Date.now();
|
||||
const argv = yargs(hideBin(process.argv)).argv;
|
||||
const newProject = {
|
||||
const new_project = {
|
||||
project:
|
||||
(!argv.p && !argv.project) ||
|
||||
_slugify(argv.p || argv.project).length === 0 ||
|
||||
!_slugify(argv.p || argv.project).length ||
|
||||
!_slugify(argv.p || argv.project).match(/[a-z0-9]/)
|
||||
? `project-${timestamp}`
|
||||
: _slugify(argv.p || argv.project),
|
||||
@@ -34,33 +43,33 @@ const newProject = {
|
||||
aesthetics: argv.aesthetics || argv.a || argv.aesthetic || false,
|
||||
};
|
||||
if (argv.file || argv.f) {
|
||||
newProject.description = fs.readFileSync(argv.file || argv.f, "utf-8");
|
||||
new_project.description = fs.readFileSync(argv.file || argv.f, "utf-8");
|
||||
}
|
||||
async function createNewProject() {
|
||||
if (!newProject.description.length) {
|
||||
async function create_new_project() {
|
||||
if (!new_project.description.length) {
|
||||
console.error(
|
||||
'Error: -d "project description" is required and cannot be empty.',
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(
|
||||
`\x1b[31minitialized generating app : ${newProject.project}\x1b[0m`,
|
||||
`\x1b[31minitialized generating app : ${new_project.project}\x1b[0m`,
|
||||
);
|
||||
console.log(
|
||||
`\x1b[34m(see ${process.env.EXPORT_APPS_ROOT}/${newProject.project}/README.md for more details)\x1b[0m` +
|
||||
`\x1b[34m(see ${process.env.EXPORT_APPS_ROOT}/${new_project.project}/README.md for more details)\x1b[0m` +
|
||||
`\n\x1b[38;5;37mto start app (api+frontend in parallel)` +
|
||||
`\n\t> cd ${process.env.EXPORT_APPS_ROOT}/${newProject.project}` +
|
||||
`\n\t> cd ${process.env.EXPORT_APPS_ROOT}/${new_project.project}` +
|
||||
`\n\t> npm i && npm run dev\x1b[0m`,
|
||||
);
|
||||
|
||||
const query = {
|
||||
pm: {
|
||||
details: {
|
||||
text: `${newProject.project != `project-${timestamp}` ? "Project '" + newProject.project + "' :" : ""} ${newProject.description}`,
|
||||
text: `${new_project.project != `project-${timestamp}` ? "Project '" + new_project.project + "' :" : ""} ${new_project.description}`,
|
||||
attachments: [],
|
||||
design: {
|
||||
aesthetics: {
|
||||
text: newProject.aesthetics,
|
||||
text: new_project.aesthetics,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -73,14 +82,16 @@ async function createNewProject() {
|
||||
const data = await cofounder.system.run({
|
||||
id: "op:PROJECT::STATE:LOAD",
|
||||
context: {
|
||||
project: newProject.project,
|
||||
...context,
|
||||
project: new_project.project,
|
||||
},
|
||||
data: {},
|
||||
});
|
||||
await cofounder.system.run({
|
||||
id: `seq:project:init:v1:resume`,
|
||||
context: {
|
||||
project: newProject.project,
|
||||
...context,
|
||||
project: new_project.project,
|
||||
},
|
||||
data: merge(data, {
|
||||
...query,
|
||||
@@ -93,35 +104,176 @@ async function createNewProject() {
|
||||
await cofounder.system.run({
|
||||
id: `seq:project:init:v1`,
|
||||
context: {
|
||||
project: newProject.project,
|
||||
...context,
|
||||
project: new_project.project,
|
||||
},
|
||||
data: query,
|
||||
});
|
||||
}
|
||||
|
||||
// Call createNewProject if command args for init project are provided
|
||||
if (newProject.project && newProject.description) {
|
||||
createNewProject();
|
||||
// Call create_new_project if command args for init project are provided
|
||||
if (new_project.project && new_project.description) {
|
||||
create_new_project();
|
||||
}
|
||||
// ----------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
// -------------------------------------------------------------- SERVER SETUP ------------------------
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 667;
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json({ limit: "5mb" }));
|
||||
app.use(express.json({ limit: "20mb" }));
|
||||
|
||||
// convert the current module's URL to a file path - necessary in ES modules to get the equivalent of __filename
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
// serve static content from ./storage ; ie. for generated layout mockup images
|
||||
app.use("/storage", express.static(path.join(__dirname, "db/storage")));
|
||||
app.use(express.static(path.join(__dirname, "dist")));
|
||||
app.use(/^\/(?!storage|api).*$/, express.static(path.join(__dirname, "dist")));
|
||||
|
||||
const server = app.listen(PORT, () => {
|
||||
console.log(
|
||||
"\x1b[33m\n> cofounder/api : server is running on port " + PORT + "\x1b[0m",
|
||||
);
|
||||
|
||||
console.log(`> debug : open browser enabled : http://localhost:${PORT}/`);
|
||||
open(`http://localhost:${PORT}/`);
|
||||
});
|
||||
|
||||
// -------------------------------------------------------- SERVER REST API PATHS ------------------------
|
||||
|
||||
app.get("/api/ping", (req, res) => {
|
||||
res.status(200).json({ message: "pong" });
|
||||
});
|
||||
|
||||
app.get("/api/projects/list", (req, res) => {
|
||||
fs.readdir("./db/projects", (err, files) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: "> cant read projects directory" });
|
||||
}
|
||||
const projects = files
|
||||
.filter((file) =>
|
||||
fs.statSync(path.join("./db/projects", file)).isDirectory(),
|
||||
)
|
||||
.map((projectDir) => {
|
||||
const yamlFilePath = path.join(
|
||||
"./db/projects",
|
||||
projectDir,
|
||||
"state/pm/user/details.yaml",
|
||||
);
|
||||
if (fs.existsSync(yamlFilePath)) {
|
||||
const fileContent = fs.readFileSync(yamlFilePath, "utf8");
|
||||
const parsedData = yaml.parse(fileContent);
|
||||
return { id: projectDir, data: parsedData.data || false };
|
||||
}
|
||||
return { id: projectDir, data: false };
|
||||
});
|
||||
res.status(200).json({ projects });
|
||||
});
|
||||
});
|
||||
|
||||
app.post("/api/utils/transcribe", async (req, res) => {
|
||||
const uid = Math.random().toString(36).slice(2, 11); // Generate a random unique ID
|
||||
const tempFilePath = path.join(__dirname, "db/storage/temp", `${uid}.webm`);
|
||||
|
||||
// Ensure the directory exists
|
||||
fs.mkdirSync(path.dirname(tempFilePath), { recursive: true });
|
||||
|
||||
/*
|
||||
app.post("/project/init", async (req, res) => {
|
||||
try {
|
||||
// see docs for steps
|
||||
res.status(200).json({ end: true });
|
||||
if (!req.body || !req.body.audio) {
|
||||
throw new Error("No audio file uploaded");
|
||||
}
|
||||
|
||||
const audioData = req.body.audio;
|
||||
const audioBuffer = Buffer.from(audioData.split(",")[1], "base64");
|
||||
|
||||
// Write the audio buffer to the temporary path
|
||||
fs.writeFileSync(tempFilePath, audioBuffer);
|
||||
|
||||
const { transcript } = await utils.openai.transcribe({ path: tempFilePath });
|
||||
res.status(200).json({ transcript });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).json({ error: "failed to init project" });
|
||||
console.error("Transcription error:", error);
|
||||
res.status(500).json({ error: "Failed to transcribe audio" });
|
||||
} finally {
|
||||
// Delete the temporary file
|
||||
fs.unlink(tempFilePath, (err) => {
|
||||
if (err) console.error("Error deleting temporary file:", err);
|
||||
});
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
app.post("/api/projects/new", async (req, res) => {
|
||||
const request = req.body;
|
||||
/*
|
||||
request : {
|
||||
project? : "project-id" || {}
|
||||
description: "",
|
||||
aeshetics?: ""
|
||||
}
|
||||
*/
|
||||
if (!request.description?.length) {
|
||||
return res.status(500).json({ error: "> no project description provided" });
|
||||
}
|
||||
const timestamp = Date.now();
|
||||
const new_project_query = {
|
||||
project:
|
||||
!request.project?.length ||
|
||||
!_slugify(request.project).length ||
|
||||
!_slugify(request.project).match(/[a-z0-9]/)
|
||||
? `project-${timestamp}`
|
||||
: _slugify(request.project),
|
||||
description: request.description,
|
||||
aesthetics: request.aesthetics?.length ? request.aesthetics : false,
|
||||
};
|
||||
|
||||
const query = {
|
||||
pm: {
|
||||
details: {
|
||||
text: `${new_project_query.project != `project-${timestamp}` ? "Project '" + new_project_query.project + "' :\n" : ""}${new_project_query.description}`,
|
||||
attachments: [],
|
||||
design: {
|
||||
aesthetics: {
|
||||
text: new_project_query.aesthetics,
|
||||
},
|
||||
},
|
||||
timestamp,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// call async
|
||||
cofounder.system.run({
|
||||
id: `seq:project:init:v1`,
|
||||
context: {
|
||||
...context, // {streams}
|
||||
project: new_project_query.project,
|
||||
},
|
||||
data: query,
|
||||
});
|
||||
|
||||
res.status(200).json({ project: new_project_query.project });
|
||||
});
|
||||
|
||||
app.post("/api/project/resume", async (req, res) => {
|
||||
const { project } = req.body;
|
||||
const resume_response = await resume_project({ project });
|
||||
console.dir({ "debug:server:project/resume": resume_response });
|
||||
setTimeout(async () => {
|
||||
await cofounder.system.run({
|
||||
id: `seq:project:init:v1`,
|
||||
context: {
|
||||
...context, // {streams}
|
||||
project,
|
||||
sequence: {
|
||||
resume: resume_response.resume,
|
||||
},
|
||||
},
|
||||
data: resume_response.data,
|
||||
});
|
||||
}, 2000);
|
||||
res.status(200).json({ project });
|
||||
});
|
||||
|
||||
const actions = {
|
||||
// map action to function ; load means load project state before passing
|
||||
@@ -138,8 +290,7 @@ const actions = {
|
||||
*/
|
||||
};
|
||||
const actionsKeys = Object.keys(actions);
|
||||
|
||||
app.post("/project/actions", async (req, res) => {
|
||||
app.post("/api/project/actions", async (req, res) => {
|
||||
/*
|
||||
in : {
|
||||
project : `exampleproject`,
|
||||
@@ -168,6 +319,7 @@ app.post("/project/actions", async (req, res) => {
|
||||
: await cofounder.system.run({
|
||||
id: "op:PROJECT::STATE:LOAD",
|
||||
context: {
|
||||
...context,
|
||||
project,
|
||||
},
|
||||
data: {},
|
||||
@@ -179,14 +331,323 @@ app.post("/project/actions", async (req, res) => {
|
||||
res.status(500).json({ error: "failed to process" });
|
||||
}
|
||||
});
|
||||
// ----------------------------------------------------------------------------------------------------
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(
|
||||
"\x1b[32m\ncofounder/api : server is running on port " + PORT + "\x1b[0m",
|
||||
);
|
||||
// -------------------------------------------------------------- SOCKET IO SETUP ------------------------
|
||||
// socket.io instance attached to express
|
||||
const io = new Server(server, {
|
||||
cors: {
|
||||
origin: "*",
|
||||
methods: ["GET", "POST"],
|
||||
},
|
||||
});
|
||||
const subscriptions = {};
|
||||
const projects = {};
|
||||
// will be sent inside context{} for system nodes to stream to at various steps
|
||||
const streams = {
|
||||
start: async ({ project, key, meta }) => {
|
||||
/*
|
||||
project , key , meta { name , desc }
|
||||
*/
|
||||
// console.dir({"debug:context:streams" : {subscriptions}} , {depth:null})
|
||||
if (subscriptions[project])
|
||||
io
|
||||
.to(subscriptions[project])
|
||||
.emit("stream$start", { timestamp: Date.now(), key, meta });
|
||||
},
|
||||
write: async ({ project, key, data }) => {
|
||||
if (subscriptions[project])
|
||||
io.to(subscriptions[project]).emit("stream$data", {
|
||||
key,
|
||||
data,
|
||||
});
|
||||
},
|
||||
end: async ({ project, key }) => {
|
||||
if (subscriptions[project])
|
||||
io
|
||||
.to(subscriptions[project])
|
||||
.emit("stream$end", { timestamp: Date.now(), key });
|
||||
},
|
||||
update: async ({ project, key, data }) => {
|
||||
if (!subscriptions[project]) return;
|
||||
// console.log('> debug:streams:update :', key)
|
||||
|
||||
// ------------ helpers --------------------------------------------------------
|
||||
if (key.includes("webapp.")) {
|
||||
// reconstruct state
|
||||
let projectSubState = {};
|
||||
const keys = key.split(".");
|
||||
let newStateUpdate = projectSubState;
|
||||
keys.forEach((k, index) => {
|
||||
if (!newStateUpdate[k]) {
|
||||
newStateUpdate[k] = index === keys.length - 1 ? data : {};
|
||||
} else if (index === keys.length - 1) {
|
||||
newStateUpdate[k] = merge(newStateUpdate[k], data);
|
||||
}
|
||||
newStateUpdate = newStateUpdate[k];
|
||||
});
|
||||
const new_update_data = {};
|
||||
let mergedKey;
|
||||
Object.keys(projectSubState.webapp).map((_type) => {
|
||||
// _type : react || layout
|
||||
Object.keys(projectSubState.webapp[_type]).map((_category) => {
|
||||
// _category : root || store || views
|
||||
Object.keys(projectSubState.webapp[_type][_category]).map((_id) => {
|
||||
// _id : app || redux || GV_Whatever || ...
|
||||
mergedKey = `webapp.${_type}.${_category}.${_id}`;
|
||||
new_update_data[mergedKey] = {};
|
||||
Object.keys(projectSubState.webapp[_type][_category][_id]).map(
|
||||
(_version) => {
|
||||
// _version : latest || {timestamp}
|
||||
new_update_data[mergedKey][_version] =
|
||||
projectSubState.webapp[_type][_category][_id][_version];
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
key = mergedKey;
|
||||
data = new_update_data[mergedKey];
|
||||
}
|
||||
io.to(subscriptions[project]).emit("state$update", {
|
||||
key,
|
||||
data,
|
||||
});
|
||||
},
|
||||
};
|
||||
const context = { streams };
|
||||
|
||||
io.on("connection", async (socket) => {
|
||||
console.log("> user connected : ", socket.id);
|
||||
socket.on("subscribe", async (project) => {
|
||||
console.log(`> user ${socket.id} subscribed to project : ${project}`);
|
||||
if (!subscriptions[project]) {
|
||||
subscriptions[project] = [];
|
||||
}
|
||||
subscriptions[project].push(socket.id);
|
||||
try {
|
||||
await load_project({ project });
|
||||
io.to(subscriptions[project]).emit("state$load", {
|
||||
timestamp: Date.now(),
|
||||
state: projects[project],
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("> cofounder/api : server error : ", e);
|
||||
}
|
||||
|
||||
/*
|
||||
console.log("> ____debug : server : op:LLM::DEBUG : run");
|
||||
const inference_debug = await cofounder.system.run({
|
||||
id: "op:LLM::DEBUG:SIMULATE",
|
||||
context: {
|
||||
...context, // {streams}
|
||||
project,
|
||||
operation: {
|
||||
key: "pm.prd",
|
||||
meta: {
|
||||
name: "PRD",
|
||||
desc: "project requirements doc",
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {}, // subscriptions[project]
|
||||
});
|
||||
const prd = inference_debug.generated;
|
||||
await cofounder.system.run({
|
||||
id: "op:PROJECT::STATE:UPDATE",
|
||||
context: {
|
||||
...context,
|
||||
project,
|
||||
},
|
||||
data: {
|
||||
operation: {
|
||||
id: "pm:prd",
|
||||
},
|
||||
type: `end`,
|
||||
content: {
|
||||
key: "pm.prd",
|
||||
data: prd,
|
||||
},
|
||||
},
|
||||
});
|
||||
const debug_webapp_data = yaml.parse(
|
||||
fs.readFileSync(
|
||||
`db/projects/foundermatchdevclone/state/webapp/code/react/views/GV_TopNav/versions/1726434529344.yaml`,
|
||||
"utf8",
|
||||
),
|
||||
);
|
||||
debug_webapp_data.data.tsx = `it be cool if this updated fr`;
|
||||
await cofounder.system.run({
|
||||
id: "op:PROJECT::STATE:UPDATE",
|
||||
context: {
|
||||
...context,
|
||||
project,
|
||||
},
|
||||
data: {
|
||||
operation: {
|
||||
id: `webapp:react:views`,
|
||||
refs: {
|
||||
id: "GV_TopNav",
|
||||
version: "1726434529344",
|
||||
},
|
||||
},
|
||||
type: `end`,
|
||||
content: {
|
||||
key: debug_webapp_data.key,
|
||||
data: debug_webapp_data.data,
|
||||
},
|
||||
},
|
||||
});
|
||||
console.log("> ____debug : server : op:LLM::DEBUG:2");
|
||||
const demo_messages = [
|
||||
{
|
||||
role: "system",
|
||||
content:
|
||||
"You are a helpful assistant that provides information about product requirements. in markdown format;",
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content:
|
||||
"Can you help me outline the product requirements for our new project? It's a rizz helper",
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
await cofounder.system.run({
|
||||
id: "op:LLM::GEN",
|
||||
context: {
|
||||
...context, // {streams}
|
||||
project,
|
||||
operation: {
|
||||
key: 'pm.prd',
|
||||
meta: {
|
||||
name: "PRD",
|
||||
desc: "product requirements document",
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
model: `gpt-4o-mini`, //`gpt-4o`,
|
||||
messages: demo_messages,
|
||||
preparser: `backticks`,
|
||||
parser: false,
|
||||
},
|
||||
})
|
||||
*/
|
||||
});
|
||||
socket.on("disconnect", () => {
|
||||
console.log("> user disconnected : ", socket.id);
|
||||
for (const project in subscriptions) {
|
||||
subscriptions[project] = subscriptions[project].filter(
|
||||
(id) => id !== socket.id,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
const load_project = async ({ project }) => {
|
||||
console.log("> load_project : start : ", project);
|
||||
const fetchedProject = await utils.load.local({
|
||||
project,
|
||||
deconstructed: true,
|
||||
});
|
||||
const fetchedProjectState = fetchedProject.state;
|
||||
const _project = fetchedProject.keymap || {};
|
||||
let projectData = {};
|
||||
Object.keys(_project)
|
||||
.filter((key) => !key.startsWith("webapp."))
|
||||
.map((key) => {
|
||||
projectData[key] = _project[key];
|
||||
});
|
||||
if (fetchedProjectState.webapp) {
|
||||
Object.keys(fetchedProjectState.webapp).map((_type) => {
|
||||
// _type : react || layout
|
||||
Object.keys(fetchedProjectState.webapp[_type]).map((_category) => {
|
||||
// _category : root || store || views
|
||||
Object.keys(fetchedProjectState.webapp[_type][_category]).map((_id) => {
|
||||
// _id : app || redux || GV_Whatever || ...
|
||||
const mergedKey = `webapp.${_type}.${_category}.${_id}`;
|
||||
projectData[mergedKey] = {};
|
||||
Object.keys(fetchedProjectState.webapp[_type][_category][_id]).map(
|
||||
(_version) => {
|
||||
// _version : latest || {timestamp}
|
||||
projectData[mergedKey][_version] =
|
||||
fetchedProjectState.webapp[_type][_category][_id][_version];
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
projects[project] = projectData;
|
||||
console.dir({
|
||||
load_project: project,
|
||||
data_keys: `${Object.keys(projects[project]).join(" , ")}`,
|
||||
});
|
||||
// only use in resume ; else check data stored in projects[project]
|
||||
return fetchedProjectState;
|
||||
};
|
||||
|
||||
const seq_projectv1_dag = [
|
||||
// dumped from makeDag() in ./build
|
||||
[], // project setup , skip
|
||||
["pm.prd"],
|
||||
["pm.frd"],
|
||||
["pm.frd", "pm.uxsmd"],
|
||||
["db.schemas", "uxsitemap.structure"],
|
||||
["db.postgres"],
|
||||
["pm.brd"],
|
||||
["backend.specifications.openapi", "backend.specifications.asyncapi"],
|
||||
["backend.server.main"],
|
||||
["pm.uxdmd"],
|
||||
["uxdatamap.structure"],
|
||||
["uxdatamap.views"],
|
||||
["webapp.react.store.redux"],
|
||||
["webapp.react.root.app"],
|
||||
["webapp.react.app.views"], // views , latest
|
||||
];
|
||||
|
||||
async function resume_project({ project }) {
|
||||
const project_data = await load_project({ project });
|
||||
const project_keys = Object.keys(projects[project]);
|
||||
let previous_phase_index = -1;
|
||||
for (let step of seq_projectv1_dag) {
|
||||
previous_phase_index++;
|
||||
if (step.length) {
|
||||
if (
|
||||
step.every((entry) => project_keys.some((key) => key.startsWith(entry)))
|
||||
) {
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
data: project_data,
|
||||
resume: previous_phase_index,
|
||||
};
|
||||
}
|
||||
|
||||
// example of how to stream to client (needs 3 steps)
|
||||
const stream_to_client = async ({ project, key, meta }) => {
|
||||
// if (!subscriptions[project]?.length) return;
|
||||
console.log(`> starting stream for project ${project}`);
|
||||
streams.start({ project, key, meta });
|
||||
const chunkSize = 20; // Define the size of each chunk
|
||||
let currentIndex = 0;
|
||||
const interval = setInterval(() => {
|
||||
const timestamp = Date.now();
|
||||
const data = texts[key].slice(currentIndex, currentIndex + chunkSize); // send chunk by chunk
|
||||
streams.write({ project, key, data });
|
||||
currentIndex += chunkSize; // Move to the next chunk
|
||||
if (currentIndex >= texts[key].length) {
|
||||
clearInterval(interval);
|
||||
streams.end({ project, key });
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
// ----------------------------------------------------------------------------------------------------
|
||||
|
||||
// -------------------------------------------------------- SERVER REST API FUNCTION CALLS ------------------------
|
||||
async function _updateProjectPreferences({ request }) {
|
||||
/*
|
||||
in : {
|
||||
@@ -204,7 +665,7 @@ async function _updateProjectPreferences({ request }) {
|
||||
const { project, query } = request;
|
||||
await cofounder.system.run({
|
||||
id: "op:PROJECT::STATE:UPDATE",
|
||||
context: { project },
|
||||
context: { ...context, project },
|
||||
data: {
|
||||
operation: {
|
||||
id: `settings:preferences:versions`,
|
||||
@@ -266,14 +727,13 @@ async function _regenerateUiComponent({ request, data }) {
|
||||
console.dir({ "debug:server:task:regen:ui": { request, task } });
|
||||
await cofounder.system.run({
|
||||
id: "WEBAPP:VIEW::GENERATE",
|
||||
context: { project },
|
||||
context: { ...context, project },
|
||||
data: {
|
||||
...data,
|
||||
task,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function _iterateUiComponent({ request, data }) {
|
||||
console.dir({ "cofounder:api:server:iterate:ui": "starts" });
|
||||
/*
|
||||
@@ -329,10 +789,11 @@ async function _iterateUiComponent({ request, data }) {
|
||||
console.dir({ "debug:server:task:regen:ui": { request, task } });
|
||||
await cofounder.system.run({
|
||||
id: "WEBAPP:VIEW::ITERATE",
|
||||
context: { project },
|
||||
context: { ...context, project },
|
||||
data: {
|
||||
...data,
|
||||
task,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
// ----------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -131,7 +131,16 @@ you're a genius`,
|
||||
const asyncapiStructure = (
|
||||
await context.run({
|
||||
id: "op:LLM::GEN",
|
||||
context,
|
||||
context: {
|
||||
...context, // {streams , project}
|
||||
operation: {
|
||||
key: "backend.specifications.asyncapi",
|
||||
meta: {
|
||||
name: "asyncAPI",
|
||||
desc: "asyncAPI specifications",
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
model: `chatgpt-4o-latest`, //`gpt-4o`,
|
||||
messages,
|
||||
|
||||
@@ -136,7 +136,16 @@ you're a genius`,
|
||||
const openapiStructure = (
|
||||
await context.run({
|
||||
id: "op:LLM::GEN",
|
||||
context,
|
||||
context: {
|
||||
...context, // {streams , project}
|
||||
operation: {
|
||||
key: "backend.specifications.openapi",
|
||||
meta: {
|
||||
name: "openAPI",
|
||||
desc: "openAPI specifications",
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
model: `chatgpt-4o-latest`, //`gpt-4o`,
|
||||
messages,
|
||||
|
||||
@@ -286,7 +286,16 @@ now do the analysis , write the full working script and specify the dependencies
|
||||
|
||||
const { generated } = await context.run({
|
||||
id: "op:LLM::GEN",
|
||||
context,
|
||||
context: {
|
||||
...context, // {streams , project}
|
||||
operation: {
|
||||
key: "backend.server.main",
|
||||
meta: {
|
||||
name: "Backend code",
|
||||
desc: "backend server main",
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
model: `chatgpt-4o-latest`, //`gpt-4o`,
|
||||
messages: messages,
|
||||
@@ -317,6 +326,23 @@ now do the analysis , write the full working script and specify the dependencies
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
/*
|
||||
await context.run({
|
||||
id: "op:PROJECT::STATE:UPDATE",
|
||||
context,
|
||||
data: {
|
||||
operation: {
|
||||
id: "backend:server:main",
|
||||
},
|
||||
type: `end`,
|
||||
content: {
|
||||
key: "backend.server.main",
|
||||
data: generatedServer,
|
||||
},
|
||||
},
|
||||
});
|
||||
*/
|
||||
|
||||
// call swarm/agument:external-apis without waiting ; it will iterate it finds any external api decorators and replace
|
||||
generatedServer = {
|
||||
...generatedServer,
|
||||
|
||||
@@ -97,7 +97,16 @@ you're a genius`
|
||||
const postgres = (
|
||||
await context.run({
|
||||
id: "op:LLM::GEN",
|
||||
context,
|
||||
context: {
|
||||
...context, // {streams , project}
|
||||
operation: {
|
||||
key: "db.postgres",
|
||||
meta: {
|
||||
name: "DB Postgresql",
|
||||
desc: "db postgres commands {tables,seed}",
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
model: `chatgpt-4o-latest`,//`gpt-4o`,
|
||||
messages,
|
||||
|
||||
@@ -112,7 +112,16 @@ you're a genius`
|
||||
const schemas = (
|
||||
await context.run({
|
||||
id: "op:LLM::GEN",
|
||||
context,
|
||||
context: {
|
||||
...context, // {streams , project}
|
||||
operation: {
|
||||
key: "db.schemas",
|
||||
meta: {
|
||||
name: "DB Schemas",
|
||||
desc: "db tables schemas",
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
model: `chatgpt-4o-latest`,//`gpt-4o`,
|
||||
messages,
|
||||
|
||||
@@ -354,7 +354,7 @@ async function designerLayoutv1ViewGenerate({ context, data }) {
|
||||
data: {
|
||||
index: "layouts",
|
||||
text: ragText,
|
||||
amount: 4,
|
||||
amount: 5,
|
||||
},
|
||||
})
|
||||
).results
|
||||
@@ -378,26 +378,34 @@ async function designerLayoutv1ViewGenerate({ context, data }) {
|
||||
rag.map(async (item) => {
|
||||
const { url } = item.image_url;
|
||||
try {
|
||||
let metadata;
|
||||
let buffer;
|
||||
if (url.startsWith("data:image/")) {
|
||||
// Handle base64 image
|
||||
const base64Data = url.split("base64,")[1];
|
||||
const buffer = Buffer.from(base64Data, "base64");
|
||||
metadata = await sharp(buffer).metadata();
|
||||
buffer = Buffer.from(base64Data, "base64");
|
||||
} else if (url.startsWith("https://")) {
|
||||
// Handle URL image
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
console.err(`designer:layoutv1:rag : failed to fetch image`);
|
||||
console.error(`designer:layoutv1:rag : failed to fetch image`);
|
||||
return null;
|
||||
}
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
metadata = await sharp(buffer).metadata();
|
||||
buffer = Buffer.from(arrayBuffer);
|
||||
} else {
|
||||
// Invalid URL format, return null to filter out later
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check image size using byteLength method
|
||||
if (Buffer.byteLength(buffer) > 4.5 * 1024 * 1024) {
|
||||
// 4.5 MB in bytes
|
||||
console.error(`> skipping : image size exceeds 4.5 MB`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const metadata = await sharp(buffer).metadata();
|
||||
|
||||
// Check image dimensions
|
||||
if (
|
||||
metadata.width >= 8000 ||
|
||||
@@ -409,13 +417,13 @@ async function designerLayoutv1ViewGenerate({ context, data }) {
|
||||
}
|
||||
return item;
|
||||
} catch (error) {
|
||||
console.error(`> skipping : error processing RAG image at ${url}:`, error);
|
||||
console.error(`> skipping : error processing RAG image : `, error);
|
||||
return null; // Return null if there's an error
|
||||
}
|
||||
}),
|
||||
)
|
||||
)
|
||||
.filter((item) => item !== null)
|
||||
.filter((item) => item)
|
||||
.slice(0, 3); // fetched more than needed in case size filtered ; typically indexed landing pages dims can be too big
|
||||
|
||||
data.task.rag = rag;
|
||||
@@ -442,7 +450,16 @@ async function designerLayoutv1ViewGenerate({ context, data }) {
|
||||
const analysisPass = (
|
||||
await context.run({
|
||||
id: "op:LLM::GEN",
|
||||
context,
|
||||
context: {
|
||||
...context, // {streams , project}
|
||||
operation: {
|
||||
key: `designer.layoutv1.analysis.${view.id}`,
|
||||
meta: {
|
||||
name: `Designer Analysis { ${view.id} }`,
|
||||
desc: "designer/layoutv1 task analysis",
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
model: `chatgpt-4o-latest`, //`gpt-4o`,
|
||||
messages: analysisPassMessages,
|
||||
@@ -458,7 +475,17 @@ async function designerLayoutv1ViewGenerate({ context, data }) {
|
||||
const svgPass = (
|
||||
await context.run({
|
||||
id: "op:LLM::GEN",
|
||||
context,
|
||||
context: {
|
||||
...context, // {streams , project}
|
||||
operation: {
|
||||
key: `designer.layoutv1.mockup.${view.id}`,
|
||||
meta: {
|
||||
name: `Designer Mockup { ${view.id} }`,
|
||||
desc: "designer/layoutv1 mockup generation",
|
||||
},
|
||||
cutoff: "```svg",
|
||||
},
|
||||
},
|
||||
data: {
|
||||
model: `chatgpt-4o-latest`, //`gpt-4o`,
|
||||
messages: svgPassMessages,
|
||||
@@ -484,7 +511,7 @@ async function designerLayoutv1ViewGenerate({ context, data }) {
|
||||
svg = await xml2js.parseStringPromise(response.svg, {
|
||||
explicitArray: true,
|
||||
});
|
||||
console.dir({ "debug:designer:layoutv1:svg": svg }, { depth: null });
|
||||
// console.dir({ "debug:designer:layoutv1:svg": svg }, { depth: null });
|
||||
if (!svg.svg.rect.filter((item) => item.$?.primitiveId).length) {
|
||||
console.error(`layout error : generated != task ; skipping`);
|
||||
}
|
||||
@@ -817,7 +844,7 @@ async function designerLayoutv1ViewIterate({ context, data }) {
|
||||
data: {
|
||||
index: "layouts",
|
||||
text: ragText,
|
||||
amount: 4,
|
||||
amount: 5,
|
||||
},
|
||||
})
|
||||
).results
|
||||
@@ -841,26 +868,34 @@ async function designerLayoutv1ViewIterate({ context, data }) {
|
||||
rag.map(async (item) => {
|
||||
const { url } = item.image_url;
|
||||
try {
|
||||
let metadata;
|
||||
let buffer;
|
||||
if (url.startsWith("data:image/")) {
|
||||
// Handle base64 image
|
||||
const base64Data = url.split("base64,")[1];
|
||||
const buffer = Buffer.from(base64Data, "base64");
|
||||
metadata = await sharp(buffer).metadata();
|
||||
buffer = Buffer.from(base64Data, "base64");
|
||||
} else if (url.startsWith("https://")) {
|
||||
// Handle URL image
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
console.err(`designer:layoutv1:rag : failed to fetch image`);
|
||||
console.error(`designer:layoutv1:rag : failed to fetch image`);
|
||||
return null;
|
||||
}
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
metadata = await sharp(buffer).metadata();
|
||||
buffer = Buffer.from(arrayBuffer);
|
||||
} else {
|
||||
// Invalid URL format, return null to filter out later
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check image size
|
||||
if (Buffer.byteLength(buffer) > 4.5 * 1024 * 1024) {
|
||||
// 4.5 MB in bytes
|
||||
console.error(`> skipping : image size exceeds 4.5 MB`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const metadata = await sharp(buffer).metadata();
|
||||
|
||||
// Check image dimensions
|
||||
if (
|
||||
metadata.width >= 8000 ||
|
||||
@@ -872,10 +907,7 @@ async function designerLayoutv1ViewIterate({ context, data }) {
|
||||
}
|
||||
return item;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`> skipping : error processing analysis RAG image at ${url}:`,
|
||||
error,
|
||||
);
|
||||
console.error(`> skipping : error processing RAG image`, error);
|
||||
return null; // Return null if there's an error
|
||||
}
|
||||
}),
|
||||
@@ -902,7 +934,17 @@ async function designerLayoutv1ViewIterate({ context, data }) {
|
||||
const svgPass = (
|
||||
await context.run({
|
||||
id: "op:LLM::GEN",
|
||||
context,
|
||||
context: {
|
||||
...context, // {streams , project}
|
||||
operation: {
|
||||
key: `designer.layoutv1.mockup.${view.id}`,
|
||||
meta: {
|
||||
name: `Designer Mockup { ${view.id} }`,
|
||||
desc: "designer/layoutv1 mockup generation",
|
||||
},
|
||||
cutoff: "```svg",
|
||||
},
|
||||
},
|
||||
data: {
|
||||
model: `chatgpt-4o-latest`, //`gpt-4o`,
|
||||
messages: svgIterateMessages,
|
||||
@@ -928,7 +970,7 @@ async function designerLayoutv1ViewIterate({ context, data }) {
|
||||
svg = await xml2js.parseStringPromise(response.svg, {
|
||||
explicitArray: true,
|
||||
});
|
||||
console.dir({ "debug:designer:layoutv1:svg": svg }, { depth: null });
|
||||
// console.dir({ "debug:designer:layoutv1:svg": svg }, { depth: null });
|
||||
if (!svg.svg.rect.filter((item) => item.$?.primitiveId).length) {
|
||||
console.error(`layout error : generated != task ; skipping`);
|
||||
}
|
||||
|
||||
@@ -16,8 +16,31 @@ async function opLlmGen({ context, data }) {
|
||||
*/
|
||||
|
||||
let { model, messages, preparser, parser, validate, query, stream } = data;
|
||||
const { project, operation, streams } = context;
|
||||
|
||||
if (operation?.key && streams) {
|
||||
await streams.start({
|
||||
project,
|
||||
key: operation.key,
|
||||
meta: operation.meta,
|
||||
});
|
||||
stream = {
|
||||
write: async (data) => {
|
||||
streams.write({
|
||||
project,
|
||||
key: operation.key,
|
||||
data,
|
||||
});
|
||||
},
|
||||
cutoff: operation?.cutoff ? operation.cutoff : false,
|
||||
};
|
||||
}
|
||||
if (!stream) stream = process.stdout;
|
||||
|
||||
if (process.env.COFOUNDER_NICKNAME?.length) {
|
||||
messages[0].content = `you are : ${process.env.COFOUNDER_NICKNAME}\n${messages[0].content}`;
|
||||
}
|
||||
|
||||
if (!preparser) {
|
||||
preparser = async ({ text }) => {
|
||||
return { text };
|
||||
@@ -46,6 +69,13 @@ async function opLlmGen({ context, data }) {
|
||||
stream,
|
||||
});
|
||||
|
||||
if (operation && streams) {
|
||||
await streams.end({
|
||||
project,
|
||||
key: operation.key,
|
||||
});
|
||||
}
|
||||
|
||||
const generated_pre = await preparser({ text }); // -> typically { text : "... extracted text ..." }
|
||||
const generated_post = await parser({
|
||||
generated: generated_pre,
|
||||
@@ -115,8 +145,72 @@ async function opLlmVectorize({ context, data }) {
|
||||
};
|
||||
}
|
||||
|
||||
async function opLlmDebugSimulate({ context, data }) {
|
||||
/*
|
||||
debug : simulate a stream
|
||||
*/
|
||||
const { project, operation } = context;
|
||||
|
||||
console.dir(
|
||||
{
|
||||
opLlmDebugSimulate: { context, data },
|
||||
},
|
||||
{ depth: null },
|
||||
);
|
||||
|
||||
const text_demo = `
|
||||
# Deleuze & Guattari
|
||||
|
||||
Gilles Deleuze (1925-1995) was a French philosopher known for his influential works in metaphysics, aesthetics, and political theory. His ideas have significantly impacted various fields, including literature, film, and art.
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### Rhizome
|
||||
Deleuze, along with Félix Guattari, introduced the concept of the **rhizome** in their work *A Thousand Plateaus*. Unlike traditional tree-like structures of knowledge, a rhizome represents a non-hierarchical and interconnected model of thought. It emphasizes multiplicity and the idea that any point can connect to any other point.
|
||||
|
||||
### Difference and Repetition
|
||||
In his book *Difference and Repetition*, Deleuze challenges the notion of identity and sameness. He argues that difference is fundamental to understanding reality, and repetition is not merely a return of the same but a process that produces new meanings.
|
||||
|
||||
### Becoming
|
||||
Deleuze's notion of **becoming** refers to the process of transformation and change. It suggests that identity is not fixed but is always in a state of flux, influenced by various factors and experiences.
|
||||
|
||||
## Conclusion
|
||||
Deleuze's philosophy encourages us to think beyond binary oppositions and embrace complexity. His work continues to inspire contemporary thought and artistic practices, making him a pivotal figure in modern philosophy.
|
||||
`;
|
||||
|
||||
await context.streams.start({
|
||||
project,
|
||||
key: operation.key,
|
||||
meta: operation.meta,
|
||||
});
|
||||
const chunkSize = 20; // Define the size of each chunk
|
||||
let currentIndex = 0;
|
||||
|
||||
while (currentIndex < text_demo.length) {
|
||||
const data = text_demo.slice(currentIndex, currentIndex + chunkSize); // send chunk by chunk
|
||||
context.streams.write({
|
||||
project,
|
||||
key: operation.key,
|
||||
data,
|
||||
});
|
||||
currentIndex += chunkSize; // Move to the next chunk
|
||||
await new Promise((resolve) => setTimeout(resolve, 100)); // Delay chunk by chunk
|
||||
}
|
||||
|
||||
await context.streams.end({
|
||||
project,
|
||||
key: operation.key,
|
||||
});
|
||||
|
||||
return {
|
||||
generated: text_demo,
|
||||
usage: {},
|
||||
};
|
||||
}
|
||||
export default {
|
||||
"op:LLM::GEN": opLlmGen,
|
||||
"op:LLM::VECTORIZE": opLlmVectorize,
|
||||
"op:LLM::VECTORIZE:CHUNK": opLlmVectorizeChunk,
|
||||
|
||||
"op:LLM::DEBUG:SIMULATE": opLlmDebugSimulate,
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import fs from "fs";
|
||||
import yaml from "yaml";
|
||||
import dotenv from "dotenv";
|
||||
import fsextra from "fs-extra";
|
||||
import { execSync } from "child_process";
|
||||
import { exec, execSync } from "child_process";
|
||||
dotenv.config();
|
||||
|
||||
/*
|
||||
@@ -107,6 +107,12 @@ const config = {
|
||||
],
|
||||
};
|
||||
|
||||
async function run_npm_i(dependenciesRootPath) {
|
||||
exec(`npm i`, {
|
||||
stdio: "inherit",
|
||||
cwd: dependenciesRootPath, // folder where package.json is
|
||||
});
|
||||
}
|
||||
async function _exportOnSave({ context, data }) {
|
||||
if (
|
||||
!(
|
||||
@@ -511,10 +517,31 @@ export default {{ID}};
|
||||
console.log(
|
||||
`\x1b[33m> dependencies updated for : ${dependenciesRootPath}\n> now running 'npm i' inside that folder\x1b[0m`,
|
||||
);
|
||||
execSync(`npm i`, {
|
||||
stdio: "inherit",
|
||||
cwd: dependenciesRootPath, // folder where package.json is
|
||||
});
|
||||
|
||||
if (context.streams) {
|
||||
await context.streams.start({
|
||||
project,
|
||||
key: "project.dependencies.install",
|
||||
meta: {
|
||||
name: "Install dependencies",
|
||||
desc: "running 'npm i' in app dir",
|
||||
},
|
||||
});
|
||||
await context.streams.write({
|
||||
project,
|
||||
key: "project.dependencies.install",
|
||||
data: `command : 'npm i'\n\n---\n\nupdate :\n\n${yaml.stringify(task.data)}\n\n---\n\npath : ${dependenciesRootPath}`,
|
||||
});
|
||||
}
|
||||
|
||||
run_npm_i(dependenciesRootPath);
|
||||
|
||||
if (context.streams) {
|
||||
await context.streams.end({
|
||||
project,
|
||||
key: "project.dependencies.install",
|
||||
});
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
@@ -584,7 +611,9 @@ async function opProjectStateUpdate({ context, data }) {
|
||||
// query.data = { ...query.data, ...content }
|
||||
}
|
||||
if (content) query.data = { ...query.data, ...content };
|
||||
console.dir({ "debug:op:project:state:update": { query } }, { depth: null });
|
||||
|
||||
console.dir({ "debug:op:project:state:update": { query } });
|
||||
|
||||
if (
|
||||
process.env.STATE_LOCAL &&
|
||||
JSON.parse(process.env.STATE_LOCAL.toLowerCase())
|
||||
@@ -605,7 +634,14 @@ async function opProjectStateUpdate({ context, data }) {
|
||||
}
|
||||
}
|
||||
fs.writeFileSync(localPath, yaml.stringify(query.data), "utf8");
|
||||
if (context.streams) {
|
||||
await context.streams.update({
|
||||
project,
|
||||
...query.data, // query.data : { key , data }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
process.env.STATE_CLOUD &&
|
||||
JSON.parse(process.env.STATE_CLOUD.toLowerCase())
|
||||
|
||||
@@ -70,7 +70,16 @@ you are a genius
|
||||
const backendStructureRequirements = (
|
||||
await context.run({
|
||||
id: "op:LLM::GEN",
|
||||
context,
|
||||
context: {
|
||||
...context, // {streams , project}
|
||||
operation: {
|
||||
key: "_pm.brd.requirements",
|
||||
meta: {
|
||||
name: "BRD Prepass",
|
||||
desc: "backend structure requirements check",
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
model: `gpt-4o-mini`, //`gpt-4o`,
|
||||
messages: backendPrompt,
|
||||
@@ -295,7 +304,16 @@ you're a genius`,
|
||||
const brd = (
|
||||
await context.run({
|
||||
id: "op:LLM::GEN",
|
||||
context,
|
||||
context: {
|
||||
...context, // {streams , project}
|
||||
operation: {
|
||||
key: "pm.brd",
|
||||
meta: {
|
||||
name: "BRD",
|
||||
desc: "backend requirements document",
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
model: `chatgpt-4o-latest`, //`gpt-4o`,
|
||||
messages,
|
||||
|
||||
@@ -107,7 +107,16 @@ you're a genius`,
|
||||
const drd = (
|
||||
await context.run({
|
||||
id: "op:LLM::GEN",
|
||||
context,
|
||||
context: {
|
||||
...context, // {streams , project}
|
||||
operation: {
|
||||
key: "pm.drd",
|
||||
meta: {
|
||||
name: "DRD",
|
||||
desc: "database requirements document",
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
model: `chatgpt-4o-latest`, //`gpt-4o`,
|
||||
messages,
|
||||
|
||||
@@ -13,46 +13,39 @@ async function pmFrdAnalysis({ context, data }) {
|
||||
role: "system",
|
||||
content: `you are an expert product manager and product designer
|
||||
your job is to consult the provided web app details & analysis PRD,
|
||||
and create a full Features Requirements Document (FRD) for it
|
||||
and create a Features Requirements Document (FRD) for it
|
||||
|
||||
the emphasis are user-facing features,
|
||||
based on the expected features and different journeys of different users in the web app
|
||||
|
||||
your generated FRD is very detailed, comprehensive and covers absolutely 100% of everything required for the web app
|
||||
your generated FRD is detailed, comprehensive and covers requirements for the web app
|
||||
|
||||
while conducting your FRD, ask yourself:
|
||||
- am i covering all 100% the purpose and functions required for the app ?
|
||||
- am i covering all 100% the expected features from all the users' perspectives? even the small details ?
|
||||
- am i covering all 100% the user journeys ?
|
||||
- am i covering all details that other product managers might have omitted from my analysis ?
|
||||
- am i making sure what i am detailing in my FRDis absolutely 100% comprehensive and ready to be put into development without any alteration ?
|
||||
- am i covering the purpose and functions required for the app ?
|
||||
- am i covering the expected features from all the users' perspectives? even the small details ?
|
||||
- am i covering the user journeys ?
|
||||
- am i covering important details in my analysis ?
|
||||
|
||||
conduct and reply with a generated comprehensive perfect FRD document, markdown-formatted
|
||||
the reply format should directly be a bulletpoints list of features items, each has 6 keys :
|
||||
|
||||
features:
|
||||
name , category , featureId (/*like 'XXXX-01' format*/) , description , detailedDiscussion , extensiveDetailedBulletpoints
|
||||
conduct and reply with a generated comprehensive FRD document, markdown-formatted
|
||||
|
||||
---
|
||||
|
||||
your FRD document will be directly put into development
|
||||
|
||||
the emphasis are user-facing features;
|
||||
functional features + interface features to cover 100% of expected features of the web app, 100% of all possible user journeys
|
||||
functional features + interface features to cover expected features of the web app
|
||||
no need to bother with non-user-facing features such as security compliance, nor similar non-user-facing technical details
|
||||
no need to bother with cases too advanced for the web app MVP features (ie. advanced analytics or multilingual or live support; ... unless specified in provided task ! )
|
||||
|
||||
emphasize user-facing features and core app MVP features
|
||||
|
||||
your reply will be directly transferred as the final FRD document
|
||||
your reply will be directly transferred as the FRD document
|
||||
so make sure the content is comprehensive and ensuing app UX is perfect as the genius you are
|
||||
if an app name is not provided, make a fitting one for your analysis and FRD
|
||||
|
||||
emphasize user-facing features and core app MVP features
|
||||
|
||||
so do not put anything else in your reply besides the FRD DOC as parseable, valid well-formatted dpc
|
||||
no extra comments or surrounding anything, only the VALID COMPREHENSIVE 100% COVERAGE AMAZING BEAUTIFUL FRD DOCUMENT
|
||||
|
||||
so do not put anything else in your reply besides the FRD DOC as parseable, valid well-formatted markdown doc
|
||||
your reply should start with : "\`\`\`markdown" and end with "\`\`\`"
|
||||
|
||||
you will be tipped $999 you are a genius`,
|
||||
@@ -73,10 +66,6 @@ ${prd}
|
||||
{
|
||||
role: "user",
|
||||
content: `implement the Features Requirements Document (FRD)
|
||||
it should span and cover all 100% of user-facing features and for all 100% of journeys required and will be directly pushed to development
|
||||
absolutely no feature would be missing ; every detail and description for 100% of every feature required
|
||||
it is expected to be 100% comprehensive and super detailed
|
||||
|
||||
you're a genius`,
|
||||
},
|
||||
];
|
||||
@@ -84,7 +73,16 @@ you're a genius`,
|
||||
const frd = (
|
||||
await context.run({
|
||||
id: "op:LLM::GEN",
|
||||
context,
|
||||
context: {
|
||||
...context, // {streams , project}
|
||||
operation: {
|
||||
key: "pm.frd",
|
||||
meta: {
|
||||
name: "FRD",
|
||||
desc: "features requirements document",
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
model: `chatgpt-4o-latest`, //`gpt-4o`,
|
||||
messages,
|
||||
|
||||
@@ -94,7 +94,16 @@ you are a genius`,
|
||||
const prd = (
|
||||
await context.run({
|
||||
id: "op:LLM::GEN",
|
||||
context,
|
||||
context: {
|
||||
...context, // {streams , project}
|
||||
operation: {
|
||||
key: "pm.prd",
|
||||
meta: {
|
||||
name: "PRD",
|
||||
desc: "product requirements document",
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
model: `chatgpt-4o-latest`, //`gpt-4o`,
|
||||
messages,
|
||||
|
||||
@@ -186,7 +186,16 @@ you're a genius`,
|
||||
const uxdmd = (
|
||||
await context.run({
|
||||
id: "op:LLM::GEN",
|
||||
context,
|
||||
context: {
|
||||
...context, // {streams , project}
|
||||
operation: {
|
||||
key: "pm.uxdmd",
|
||||
meta: {
|
||||
name: "UXDMD",
|
||||
desc: "ux datamap document",
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
model: `chatgpt-4o-latest`, // `chatgpt-4o-latest`,//`gpt-4o`,
|
||||
messages,
|
||||
|
||||
@@ -84,9 +84,17 @@ ask yourself:
|
||||
---
|
||||
|
||||
important :
|
||||
|
||||
> do not many any "Container" views (like some GV_GlobalContainer or something) ; DO NOT make any container views to contain other views inside of them !
|
||||
only make unique UV_* or GV_* shared views : views that serve a functional purpose ; not container views !
|
||||
|
||||
> GV_* shared views are INDEPENDENT from UV_* !
|
||||
UV_* views DO NOT INTEGRATE GV_* views inside them !
|
||||
they simply share screen space !!
|
||||
do not make any UV_* functionality dependent on GV_*
|
||||
do not make any GV_* functionality dependent on GV_*
|
||||
they are independent , they do not include each other , shared simply means sharing screen space , not functionalities !
|
||||
|
||||
---
|
||||
|
||||
your reply will be directly transferred as the final UX Sitemap Analysis Document, so do not put anything else in your reply besides the UX Sitemap analysis document
|
||||
@@ -124,7 +132,16 @@ you're a genius`,
|
||||
const uxsmd = (
|
||||
await context.run({
|
||||
id: "op:LLM::GEN",
|
||||
context,
|
||||
context: {
|
||||
...context, // {streams , project}
|
||||
operation: {
|
||||
key: "pm.uxsmd",
|
||||
meta: {
|
||||
name: "UXSMD",
|
||||
desc: "ux sitemap document",
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
model: `chatgpt-4o-latest`, //`gpt-4o`,
|
||||
messages,
|
||||
|
||||
@@ -309,7 +309,16 @@ async function swarmAugmentBackendExternalapis({ context, data }) {
|
||||
const analysisPass = (
|
||||
await context.run({
|
||||
id: "op:LLM::GEN",
|
||||
context,
|
||||
context: {
|
||||
...context, // {streams , project}
|
||||
operation: {
|
||||
key: "swarm.augment.analysis",
|
||||
meta: {
|
||||
name: "Swarm Post-Gen Check",
|
||||
desc: "analysis pass",
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
model: `chatgpt-4o-latest`, //`gpt-4o`,
|
||||
messages: messagesAnalysis,
|
||||
@@ -324,7 +333,16 @@ async function swarmAugmentBackendExternalapis({ context, data }) {
|
||||
const messagesImplementMerge = await promptImplementMerge({ context, data });
|
||||
const { generated } = await context.run({
|
||||
id: "op:LLM::GEN",
|
||||
context,
|
||||
context: {
|
||||
...context, // {streams , project}
|
||||
operation: {
|
||||
key: "swarm.augment.implement",
|
||||
meta: {
|
||||
name: "Swarm Code Update",
|
||||
desc: "implement changes & merge",
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
model: `chatgpt-4o-latest`, //`gpt-4o`,
|
||||
messages: messagesImplementMerge,
|
||||
|
||||
@@ -151,7 +151,16 @@ ${uxdmd}
|
||||
const uxdatamapStructure = (
|
||||
await context.run({
|
||||
id: "op:LLM::GEN",
|
||||
context,
|
||||
context: {
|
||||
...context, // {streams , project}
|
||||
operation: {
|
||||
key: "uxdatamap.structure",
|
||||
meta: {
|
||||
name: "UX Datamap Structure",
|
||||
desc: "define the app's data architecture",
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
model: `chatgpt-4o-latest`, //`chatgpt-4o-latest`, // `chatgpt-4o-latest`,//`gpt-4o`,
|
||||
messages,
|
||||
@@ -227,7 +236,7 @@ async function uxDatamapViews({ context, data }) {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const GVs = Object.keys(uxsitemap?.structure?.views?.shared) || {};
|
||||
if (GVs.length) {
|
||||
tasks.push({
|
||||
@@ -470,7 +479,16 @@ you're a genius`,
|
||||
const views = (
|
||||
await context.run({
|
||||
id: "op:LLM::GEN",
|
||||
context,
|
||||
context: {
|
||||
...context, // {streams , project}
|
||||
operation: {
|
||||
key: "uxdatamap.views",
|
||||
meta: {
|
||||
name: "UX Datamap Views",
|
||||
desc: "define views <> data architecture",
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
model: `chatgpt-4o-latest`, //`chatgpt-4o-latest`, // `chatgpt-4o-latest`,//`gpt-4o`,
|
||||
messages,
|
||||
|
||||
@@ -93,7 +93,16 @@ You're a genius do a great job`,
|
||||
const uxsitemapStructure = (
|
||||
await context.run({
|
||||
id: "op:LLM::GEN",
|
||||
context,
|
||||
context: {
|
||||
...context, // {streams , project}
|
||||
operation: {
|
||||
key: "uxsitemap.structure",
|
||||
meta: {
|
||||
name: "UX Sitemap Structure",
|
||||
desc: "define sitemap <> views architecture",
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
model: `chatgpt-4o-latest`, // `chatgpt-4o-latest`,//`gpt-4o`,
|
||||
messages,
|
||||
|
||||
@@ -10,8 +10,8 @@ async function promptRoot({ context, data }) {
|
||||
const { uxsitemap, uxdatamap, webapp } = data;
|
||||
|
||||
const viewsImportHead = [
|
||||
...( Object.keys(uxsitemap?.structure?.views?.shared) || {} ),
|
||||
...( Object.keys(uxsitemap?.structure?.views?.unique) || {} ),
|
||||
...(Object.keys(uxsitemap?.structure?.views?.shared) || {}),
|
||||
...(Object.keys(uxsitemap?.structure?.views?.unique) || {}),
|
||||
]
|
||||
.map((viewId) => {
|
||||
return `import ${viewId} from '@/components/views/${viewId}.tsx';`;
|
||||
@@ -194,7 +194,16 @@ async function webappRootGenerate({ context, data }) {
|
||||
|
||||
const { generated } = await context.run({
|
||||
id: "op:LLM::GEN",
|
||||
context,
|
||||
context: {
|
||||
...context, // {streams , project}
|
||||
operation: {
|
||||
key: `webapp.react.root.app.latest`,
|
||||
meta: {
|
||||
name: "Webapp Root Component",
|
||||
desc: "react App.tsx component code",
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
model: `chatgpt-4o-latest`, //`gpt-4o`,
|
||||
messages: messages,
|
||||
|
||||
@@ -194,7 +194,16 @@ async function webappStoreGenerate({ context, data }) {
|
||||
|
||||
const { generated } = await context.run({
|
||||
id: "op:LLM::GEN",
|
||||
context,
|
||||
context: {
|
||||
...context, // {streams , project}
|
||||
operation: {
|
||||
key: `webapp.react.store.redux.latest`,
|
||||
meta: {
|
||||
name: "Webapp Data Store",
|
||||
desc: "redux data store component code",
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
model: `chatgpt-4o-latest`, //`gpt-4o`,
|
||||
messages: messages,
|
||||
|
||||
@@ -16,7 +16,7 @@ async function webappViewGenerateMulti({ context, data }) {
|
||||
};
|
||||
|
||||
const tasks = [
|
||||
...( Object.keys(views?.unique) || {} ).map((viewId) => {
|
||||
...(Object.keys(views?.unique) || {}).map((viewId) => {
|
||||
return {
|
||||
task: {
|
||||
type: "view",
|
||||
@@ -28,7 +28,7 @@ async function webappViewGenerateMulti({ context, data }) {
|
||||
},
|
||||
};
|
||||
}),
|
||||
...( Object.keys(views?.shared) || {} ).map((viewId) => {
|
||||
...(Object.keys(views?.shared) || {}).map((viewId) => {
|
||||
return {
|
||||
task: {
|
||||
type: "view",
|
||||
@@ -502,7 +502,16 @@ async function webappViewGenerate({ context, data }) {
|
||||
|
||||
const { generated } = await context.run({
|
||||
id: "op:LLM::GEN",
|
||||
context,
|
||||
context: {
|
||||
...context, // {streams , project}
|
||||
operation: {
|
||||
key: `webapp.react.views.${view.id}.latest`,
|
||||
meta: {
|
||||
name: `Webapp View Implement { ${view.id} }`,
|
||||
desc: "react view, functional pass",
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
model: `chatgpt-4o-latest`, //`gpt-4o`,
|
||||
messages: messagesFunctional,
|
||||
@@ -679,7 +688,16 @@ async function webappViewGenerate({ context, data }) {
|
||||
|
||||
const { generated } = await context.run({
|
||||
id: "op:LLM::GEN",
|
||||
context,
|
||||
context: {
|
||||
...context, // {streams , project}
|
||||
operation: {
|
||||
key: `webapp.react.views.${view.id}.latest`,
|
||||
meta: {
|
||||
name: `Webapp View Redesign { ${view.id} }`,
|
||||
desc: "react view, redesign pass",
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
model: `chatgpt-4o-latest`, //`gpt-4o`,
|
||||
messages: messagesRedesign,
|
||||
@@ -1222,7 +1240,16 @@ async function webappViewIterate({ context, data }) {
|
||||
|
||||
const { generated } = await context.run({
|
||||
id: "op:LLM::GEN",
|
||||
context,
|
||||
context: {
|
||||
...context, // {streams , project}
|
||||
operation: {
|
||||
key: `webapp.react.views.${view.id}.latest`,
|
||||
meta: {
|
||||
name: `Webapp View Iterate { ${view.id} }`,
|
||||
desc: "react view iteration , code only",
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
model: `chatgpt-4o-latest`, //`gpt-4o`,
|
||||
messages: promptMessagesNoDesigner,
|
||||
@@ -1377,7 +1404,16 @@ async function webappViewIterate({ context, data }) {
|
||||
|
||||
const { generated } = await context.run({
|
||||
id: "op:LLM::GEN",
|
||||
context,
|
||||
context: {
|
||||
...context, // {streams , project}
|
||||
operation: {
|
||||
key: `webapp.react.views.${view.id}.latest`,
|
||||
meta: {
|
||||
name: `Webapp View Iterate { ${view.id} }`,
|
||||
desc: "react view iteration , with designer",
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
model: `chatgpt-4o-latest`, //`gpt-4o`,
|
||||
messages: mesagesIterateWithDesigner,
|
||||
|
||||
@@ -13,7 +13,7 @@ nodes:
|
||||
- generated
|
||||
- usage
|
||||
queue:
|
||||
concurrency: 1
|
||||
concurrency: 2
|
||||
op:LLM::VECTORIZE:
|
||||
desc: "{texts} -> {vectors}"
|
||||
in:
|
||||
@@ -31,3 +31,5 @@ nodes:
|
||||
- usage
|
||||
queue:
|
||||
concurrency: 50
|
||||
op:LLM::DEBUG:SIMULATE:
|
||||
desc: "simulate llm streams for dev"
|
||||
|
||||
@@ -78,17 +78,33 @@ async function inference({
|
||||
max_tokens: 8192,
|
||||
messages: converted.messages,
|
||||
});
|
||||
|
||||
let text = "";
|
||||
let usage = {};
|
||||
|
||||
let cutoff_reached = false;
|
||||
let chunks_buffer = "";
|
||||
let chunks_iterator = 0;
|
||||
const chunks_every = 5;
|
||||
for await (const event of streaming) {
|
||||
if (
|
||||
event.type === "content_block_delta" &&
|
||||
event.delta.type === "text_delta"
|
||||
) {
|
||||
const content = event.delta.text;
|
||||
stream.write(content);
|
||||
text += content;
|
||||
if (content) {
|
||||
text += content;
|
||||
chunks_buffer += content;
|
||||
chunks_iterator++;
|
||||
if (stream?.cutoff) {
|
||||
if (!cutoff_reached && text.includes(stream.cutoff)) {
|
||||
cutoff_reached = true;
|
||||
}
|
||||
}
|
||||
if (!(chunks_iterator % chunks_every)) {
|
||||
stream.write(!cutoff_reached ? chunks_buffer : " ...");
|
||||
chunks_buffer = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stream.write("\n");
|
||||
|
||||
@@ -40,7 +40,7 @@ async function readLocal({ project }) {
|
||||
return db;
|
||||
}
|
||||
|
||||
async function loadLocal({ project }) {
|
||||
async function loadLocal({ project, deconstructed = false }) {
|
||||
const db = await readLocal({ project });
|
||||
let state = {};
|
||||
/*
|
||||
@@ -95,8 +95,9 @@ async function loadLocal({ project }) {
|
||||
current = current[k];
|
||||
});
|
||||
});
|
||||
|
||||
return state;
|
||||
if (!deconstructed) return state;
|
||||
const keymap = Object.fromEntries(db.map(({ key, data }) => [key, data]));
|
||||
return { state, keymap };
|
||||
}
|
||||
|
||||
async function readCloud({ project }) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import fs from "fs";
|
||||
import OpenAI from "openai";
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
@@ -22,17 +23,33 @@ async function inference({
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
});
|
||||
|
||||
let text = "";
|
||||
let usage = {};
|
||||
let cutoff_reached = false;
|
||||
let chunks_buffer = "";
|
||||
let chunks_iterator = 0;
|
||||
const chunks_every = 5;
|
||||
for await (const chunk of streaming) {
|
||||
const content = chunk.choices[0]?.delta?.content || "";
|
||||
if (content) {
|
||||
stream.write(content);
|
||||
text += content;
|
||||
chunks_buffer += content;
|
||||
chunks_iterator++;
|
||||
if (stream?.cutoff) {
|
||||
if (!cutoff_reached && text.includes(stream.cutoff)) {
|
||||
cutoff_reached = true;
|
||||
}
|
||||
}
|
||||
if (!(chunks_iterator % chunks_every)) {
|
||||
stream.write(!cutoff_reached ? chunks_buffer : " ...");
|
||||
chunks_buffer = "";
|
||||
}
|
||||
}
|
||||
if (chunk.usage) usage = { ...chunk.usage };
|
||||
}
|
||||
stream.write(`\n`);
|
||||
|
||||
return {
|
||||
text,
|
||||
usage: { model, ...usage },
|
||||
@@ -55,7 +72,20 @@ async function vectorize({
|
||||
};
|
||||
}
|
||||
|
||||
async function transcribe({ path }) {
|
||||
console.dir({ "debug:utils:openai:transcribe:received": { path } });
|
||||
const response = await openai.audio.transcriptions.create({
|
||||
file: fs.createReadStream(path),
|
||||
model: "whisper-1",
|
||||
});
|
||||
console.dir({ "debug:utils:openai:transcribe": { path, response } });
|
||||
return {
|
||||
transcript: response.text,
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
inference,
|
||||
vectorize,
|
||||
transcribe,
|
||||
};
|
||||
|
||||
@@ -69,11 +69,12 @@ async function _render({
|
||||
const pngData = resvg.render();
|
||||
const pngBuffer = pngData.asPng();
|
||||
|
||||
// console.info('Original SVG Size:', `${resvg.width} x ${resvg.height}`)
|
||||
/*
|
||||
console.info(
|
||||
"> utils.render svg size : ",
|
||||
`${pngData.width}x${pngData.height}`,
|
||||
);
|
||||
*/
|
||||
|
||||
const uid = crypto.randomUUID();
|
||||
|
||||
|
||||
@@ -3,16 +3,20 @@ import fs from "fs";
|
||||
import { PGlite } from "@electric-sql/pglite";
|
||||
dotenv.config();
|
||||
|
||||
const postgres = new PGlite(`./db`);
|
||||
const dbInitCommands = fs
|
||||
.readFileSync(`./db.sql`, "utf-8")
|
||||
.toString()
|
||||
.split(/(?=CREATE |INSERT)/);
|
||||
for (let cmd of dbInitCommands) {
|
||||
console.dir({ "backend:db:init:command": cmd });
|
||||
try {
|
||||
await postgres.exec(cmd);
|
||||
} catch (e) {
|
||||
console.dir({ "backend:db:init:error": e });
|
||||
const dbPath = `./db`;
|
||||
|
||||
if (!fs.existsSync(dbPath)) {
|
||||
const postgres = new PGlite(dbPath);
|
||||
const dbInitCommands = fs
|
||||
.readFileSync(`./db.sql`, "utf-8")
|
||||
.toString()
|
||||
.split(/(?=CREATE TABLE |INSERT INTO)/);
|
||||
for (let cmd of dbInitCommands) {
|
||||
console.dir({ "backend:db:init:command": cmd });
|
||||
try {
|
||||
await postgres.exec(cmd);
|
||||
} catch (e) {
|
||||
console.dir({ "backend:db:init:error": e });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from "react";
|
||||
import { Route, Routes } from "react-router-dom";
|
||||
import cofounder from "@/assets/cofounder.webp";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
const App: React.FC = () => {
|
||||
return (
|
||||
|
||||
@@ -110,7 +110,7 @@ export default {
|
||||
*/
|
||||
code = code.replaceAll(
|
||||
`{COFOUNDER_LOCAL_API_BASE_URL}`,
|
||||
`http://localhost:667`,
|
||||
`http://localhost:667/api`,
|
||||
);
|
||||
if (path.includes(`src/App.tsx`)) {
|
||||
return await editSectionsAndViews({ path, code });
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
/*
|
||||
cmdk <> genui-* <> relayer? store
|
||||
*/
|
||||
import { configureStore } from "@reduxjs/toolkit";
|
||||
import { Provider } from "react-redux";
|
||||
|
||||
// Create a Redux store
|
||||
const store = configureStore({
|
||||
reducer: {}, // Add your reducers here
|
||||
});
|
||||
|
||||
// Export the store as default
|
||||
export default store;
|
||||
@@ -1,9 +0,0 @@
|
||||
import { configureStore } from "@reduxjs/toolkit";
|
||||
|
||||
// Create a Redux store
|
||||
const store = configureStore({
|
||||
reducer: {}, // Empty reducer to prevent crashing
|
||||
});
|
||||
|
||||
// Export the store as default
|
||||
export default store;
|
||||
26
cofounder/dashboard/.gitignore
vendored
Normal file
26
cofounder/dashboard/.gitignore
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
public
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
2
cofounder/dashboard/.prettierignore
Normal file
2
cofounder/dashboard/.prettierignore
Normal file
@@ -0,0 +1,2 @@
|
||||
gits/
|
||||
vectordb/
|
||||
4
cofounder/dashboard/.prettierrc
Normal file
4
cofounder/dashboard/.prettierrc
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"tabWidth": 1,
|
||||
"useTabs": true
|
||||
}
|
||||
1
cofounder/dashboard/README.md
Normal file
1
cofounder/dashboard/README.md
Normal file
@@ -0,0 +1 @@
|
||||
[dashboard]
|
||||
20
cofounder/dashboard/components.json
Normal file
20
cofounder/dashboard/components.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "default",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.js",
|
||||
"css": "src/index.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": false,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
}
|
||||
}
|
||||
26
cofounder/dashboard/eslint.config.js
Normal file
26
cofounder/dashboard/eslint.config.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import js from "@eslint/js";
|
||||
import globals from "globals";
|
||||
import reactHooks from "eslint-plugin-react-hooks";
|
||||
import reactRefresh from "eslint-plugin-react-refresh";
|
||||
import tseslint from "typescript-eslint";
|
||||
|
||||
export default tseslint.config({
|
||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||
files: ["**/*.{ts,tsx}"],
|
||||
ignores: ["dist"],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
plugins: {
|
||||
"react-hooks": reactHooks,
|
||||
"react-refresh": reactRefresh,
|
||||
},
|
||||
rules: {
|
||||
...reactHooks.configs.recommended.rules,
|
||||
"react-refresh/only-export-components": [
|
||||
"warn",
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
});
|
||||
29
cofounder/dashboard/index.html
Normal file
29
cofounder/dashboard/index.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Karla:wght@100;200;300;400;500;600;700;800;900&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@100;200;300;400;500;600;700;800;900&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<title>Cofounder Dashboard</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
<script>
|
||||
document.addEventListener("keydown", (event) => {
|
||||
if (event.ctrlKey && event.key === "k") {
|
||||
console.log("Ctrl+K pressed!");
|
||||
event.preventDefault(); // Prevent doing a google search
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
9296
cofounder/dashboard/package-lock.json
generated
Normal file
9296
cofounder/dashboard/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
108
cofounder/dashboard/package.json
Normal file
108
cofounder/dashboard/package.json
Normal file
@@ -0,0 +1,108 @@
|
||||
{
|
||||
"name": "dashboard",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@heroicons/react": "^2.1.5",
|
||||
"@hookform/resolvers": "^3.9.0",
|
||||
"@radix-ui/react-accordion": "^1.2.0",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.1",
|
||||
"@radix-ui/react-aspect-ratio": "^1.1.0",
|
||||
"@radix-ui/react-avatar": "^1.1.0",
|
||||
"@radix-ui/react-checkbox": "^1.1.1",
|
||||
"@radix-ui/react-collapsible": "^1.1.0",
|
||||
"@radix-ui/react-context-menu": "^2.2.1",
|
||||
"@radix-ui/react-dialog": "^1.1.1",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
||||
"@radix-ui/react-hover-card": "^1.1.1",
|
||||
"@radix-ui/react-label": "^2.1.0",
|
||||
"@radix-ui/react-menubar": "^1.1.1",
|
||||
"@radix-ui/react-navigation-menu": "^1.2.0",
|
||||
"@radix-ui/react-popover": "^1.1.1",
|
||||
"@radix-ui/react-progress": "^1.1.0",
|
||||
"@radix-ui/react-radio-group": "^1.2.0",
|
||||
"@radix-ui/react-scroll-area": "^1.1.0",
|
||||
"@radix-ui/react-select": "^2.1.1",
|
||||
"@radix-ui/react-separator": "^1.1.0",
|
||||
"@radix-ui/react-slider": "^1.2.0",
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"@radix-ui/react-switch": "^1.1.0",
|
||||
"@radix-ui/react-tabs": "^1.1.0",
|
||||
"@radix-ui/react-toast": "^1.2.1",
|
||||
"@radix-ui/react-toggle": "^1.1.0",
|
||||
"@radix-ui/react-toggle-group": "^1.1.0",
|
||||
"@radix-ui/react-tooltip": "^1.1.2",
|
||||
"@reduxjs/toolkit": "^2.2.7",
|
||||
"@trbn/jsoncanvas": "^1.0.6",
|
||||
"@xyflow/react": "^12.3.0",
|
||||
"axios": "^1.7.7",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
"colormap": "^2.3.2",
|
||||
"d3": "^7.9.0",
|
||||
"date-fns": "^4.0.0-beta.1",
|
||||
"embla-carousel-autoplay": "^8.3.0",
|
||||
"embla-carousel-react": "^8.3.0",
|
||||
"framer-motion": "^11.5.4",
|
||||
"input-otp": "^1.2.4",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lucide-react": "^0.439.0",
|
||||
"next-themes": "^0.3.0",
|
||||
"react": "^18.3.1",
|
||||
"react-day-picker": "^8.10.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-hook-form": "^7.53.0",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-redux": "^9.1.2",
|
||||
"react-resizable-panels": "^2.1.3",
|
||||
"react-router-dom": "^6.26.0",
|
||||
"react-speech-recognition": "^3.10.0",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"react-tooltip": "^5.28.0",
|
||||
"recharts": "^2.12.7",
|
||||
"redux": "^5.0.1",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-thunk": "^3.1.0",
|
||||
"regenerator-runtime": "^0.14.1",
|
||||
"rehype-highlight": "^7.0.0",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"remark-frontmatter": "^5.0.0",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"socket.io-client": "^4.7.5",
|
||||
"sonner": "^1.5.0",
|
||||
"tailwind-merge": "^2.5.2",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"use-react-screenshot": "^4.0.0",
|
||||
"use-screenshot-hook": "^1.0.2",
|
||||
"vaul": "^0.9.3",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.8.0",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^22.1.0",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^9.8.0",
|
||||
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.9",
|
||||
"globals": "^15.9.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"postcss": "^8.4.41",
|
||||
"tailwindcss": "^3.4.8",
|
||||
"typescript": "^5.5.3",
|
||||
"typescript-eslint": "^8.0.0",
|
||||
"vite": "^5.4.0"
|
||||
}
|
||||
}
|
||||
6
cofounder/dashboard/postcss.config.js
Normal file
6
cofounder/dashboard/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
97
cofounder/dashboard/src/App.tsx
Normal file
97
cofounder/dashboard/src/App.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import React, { useCallback, useState, useEffect } from "react";
|
||||
import { Route, Routes, useLocation } from "react-router-dom";
|
||||
import cofounder from "@/assets/cofounder.webp";
|
||||
import Sidebar from "@/components/views/sidebar";
|
||||
import ProjectsList from "@/components/views/projects-list";
|
||||
import ComponentsDesigner from "@/components/views/component-designer";
|
||||
import Settings from "@/components/views/settings";
|
||||
import Project from "@/components/views/project";
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [pingServer, setPingServer] = useState(false);
|
||||
const [pingServerChecked, setPingServerChecked] = useState(false);
|
||||
const location = useLocation();
|
||||
|
||||
const SERVER_LOCAL_URL = "http://localhost:667/api";
|
||||
useEffect(() => {
|
||||
const checkPingServer = async () => {
|
||||
try {
|
||||
const response = await fetch(`${SERVER_LOCAL_URL}/ping`);
|
||||
if (response.ok) {
|
||||
setPingServer(true);
|
||||
} else {
|
||||
setPingServer(false);
|
||||
}
|
||||
} catch (error) {
|
||||
setPingServer(false);
|
||||
}
|
||||
setPingServerChecked(true);
|
||||
};
|
||||
checkPingServer();
|
||||
}, []);
|
||||
if (!pingServerChecked) return <></>;
|
||||
|
||||
return (
|
||||
<>
|
||||
{(pingServer && (
|
||||
<>
|
||||
<div className="flex h-screen">
|
||||
{!location.pathname.startsWith("/project/") && <Sidebar />}
|
||||
<div className="flex-1 overflow-auto">
|
||||
<Routes>
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
<>
|
||||
<div className="container text-white mx-auto w-full max-w-[90vw] xl:max-w-[60vw] p-12 mt-12 text-left whitespace-pre-line break-words">
|
||||
<section className="pb-4 mb-4 text-center">
|
||||
<a
|
||||
href="https://github.com/raidendotai/cofounder"
|
||||
target="_blank"
|
||||
className="opacity-100 hover:opacity-90 duration-200"
|
||||
>
|
||||
<img
|
||||
className="rounded rounded-xl max-w-[90vw] md:max-w-[35vw] mx-auto"
|
||||
src={cofounder}
|
||||
/>
|
||||
</a>
|
||||
</section>
|
||||
<h2 className="mt-4 text-2xl opacity-50 font-light text-center uppercase">
|
||||
early alpha release
|
||||
</h2>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/projects"
|
||||
element={
|
||||
<div className="container text-white mx-auto w-full max-w-[90vw] xl:max-w-[80vw] p-6 mt-6 text-left whitespace-pre-line break-words">
|
||||
<ProjectsList />
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<Route path="/project/:project" element={<Project />} />
|
||||
<Route path="/playground/designer" element={<ComponentsDesigner />} />
|
||||
<Route path="/settings" element={<Settings />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)) || (
|
||||
<>
|
||||
<div className="flex items-center justify-center h-screen w-full text-white">
|
||||
<h1 className="text-2xl font-light opacity-50 whitespace-pre-wrap break-all">
|
||||
{`{ local cofounder/api server at \`${SERVER_LOCAL_URL}\` not reachable }`}
|
||||
<br />
|
||||
<br />
|
||||
{`>\tmake sure local cofounder server is launched\n\t( use \`npm run start\` in cofounder/api/ )`}
|
||||
</h1>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
29
cofounder/dashboard/src/App.tsx.bak
Normal file
29
cofounder/dashboard/src/App.tsx.bak
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from "react";
|
||||
import { Route, Routes } from "react-router-dom";
|
||||
import cofounder from "@/assets/cofounder.webp";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
const App: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="container mx-auto w-full xl:w-[60vw] p-12 mt-12 text-left whitespace-pre-line break-words">
|
||||
<section className="pb-4 mb-4 text-center">
|
||||
<a
|
||||
href="https://github.com/raidendotai/cofounder"
|
||||
target="_blank"
|
||||
className="opacity-100 hover:opacity-90 duration-200"
|
||||
>
|
||||
<img
|
||||
className="rounded rounded-xl md:max-w-[30vw] mx-auto"
|
||||
src={cofounder}
|
||||
/>
|
||||
</a>
|
||||
<h1 className="mt-8 text-2xl">Vite + React</h1>
|
||||
<h1 className="mt-2">your app will update here as it generates</h1>
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
13
cofounder/dashboard/src/app-wrapper.tsx
Normal file
13
cofounder/dashboard/src/app-wrapper.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React, { useCallback, useState, useEffect } from "react";
|
||||
import App from "@/App.tsx";
|
||||
|
||||
const AppWrapper: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
{/*<Cmdl />*/}
|
||||
<App />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppWrapper;
|
||||
5
cofounder/dashboard/src/app.css
Normal file
5
cofounder/dashboard/src/app.css
Normal file
@@ -0,0 +1,5 @@
|
||||
#root {
|
||||
max-width: 90vw;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
BIN
cofounder/dashboard/src/assets/cofounder.webp
Normal file
BIN
cofounder/dashboard/src/assets/cofounder.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 61 KiB |
@@ -0,0 +1,59 @@
|
||||
import React from "react";
|
||||
import { getBezierPath } from "@xyflow/react";
|
||||
|
||||
import { getEdgeParams } from "@/components/flow/helpers/utils.js";
|
||||
|
||||
function FloatingConnectionLine({
|
||||
toX,
|
||||
toY,
|
||||
fromPosition,
|
||||
toPosition,
|
||||
fromNode,
|
||||
}) {
|
||||
if (!fromNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const targetNode = {
|
||||
id: "connection-target",
|
||||
measured: {
|
||||
width: 1,
|
||||
height: 1,
|
||||
},
|
||||
internals: {
|
||||
positionAbsolute: { x: toX, y: toY },
|
||||
},
|
||||
};
|
||||
|
||||
const { sx, sy } = getEdgeParams(fromNode, targetNode);
|
||||
const [edgePath] = getBezierPath({
|
||||
sourceX: sx,
|
||||
sourceY: sy,
|
||||
sourcePosition: fromPosition,
|
||||
targetPosition: toPosition,
|
||||
targetX: toX,
|
||||
targetY: toY,
|
||||
});
|
||||
|
||||
return (
|
||||
<g>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#222"
|
||||
strokeWidth={1.5}
|
||||
className="animated"
|
||||
d={edgePath}
|
||||
/>
|
||||
<circle
|
||||
cx={toX}
|
||||
cy={toY}
|
||||
fill="#fff"
|
||||
r={3}
|
||||
stroke="#222"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
export default FloatingConnectionLine;
|
||||
@@ -0,0 +1,38 @@
|
||||
import { getBezierPath, useInternalNode } from "@xyflow/react";
|
||||
|
||||
import { getEdgeParams } from "@/components/flow/helpers/utils.js";
|
||||
|
||||
function FloatingEdge({ id, source, target, markerEnd, style }) {
|
||||
const sourceNode = useInternalNode(source);
|
||||
const targetNode = useInternalNode(target);
|
||||
|
||||
if (!sourceNode || !targetNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams(
|
||||
sourceNode,
|
||||
targetNode,
|
||||
);
|
||||
|
||||
const [edgePath] = getBezierPath({
|
||||
sourceX: sx,
|
||||
sourceY: sy,
|
||||
sourcePosition: sourcePos,
|
||||
targetPosition: targetPos,
|
||||
targetX: tx,
|
||||
targetY: ty,
|
||||
});
|
||||
|
||||
return (
|
||||
<path
|
||||
id={id}
|
||||
className="react-flow__edge-path"
|
||||
d={edgePath}
|
||||
markerEnd={markerEnd}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default FloatingEdge;
|
||||
10
cofounder/dashboard/src/components/flow/helpers/floating.css
Normal file
10
cofounder/dashboard/src/components/flow/helpers/floating.css
Normal file
@@ -0,0 +1,10 @@
|
||||
.floatingedges {
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.floatingedges .react-flow__handle {
|
||||
opacity: 0;
|
||||
}
|
||||
100
cofounder/dashboard/src/components/flow/helpers/utils.js
Normal file
100
cofounder/dashboard/src/components/flow/helpers/utils.js
Normal file
@@ -0,0 +1,100 @@
|
||||
import { Position, MarkerType } from "@xyflow/react";
|
||||
|
||||
// this helper function returns the intersection point
|
||||
// of the line between the center of the intersectionNode and the target node
|
||||
function getNodeIntersection(intersectionNode, targetNode) {
|
||||
// https://math.stackexchange.com/questions/1724792/an-algorithm-for-finding-the-intersection-point-between-a-center-of-vision-and-a
|
||||
const { width: intersectionNodeWidth, height: intersectionNodeHeight } =
|
||||
intersectionNode.measured;
|
||||
const intersectionNodePosition = intersectionNode.internals.positionAbsolute;
|
||||
const targetPosition = targetNode.internals.positionAbsolute;
|
||||
|
||||
const w = intersectionNodeWidth / 2;
|
||||
const h = intersectionNodeHeight / 2;
|
||||
|
||||
const x2 = intersectionNodePosition.x + w;
|
||||
const y2 = intersectionNodePosition.y + h;
|
||||
const x1 = targetPosition.x + targetNode.measured.width / 2;
|
||||
const y1 = targetPosition.y + targetNode.measured.height / 2;
|
||||
|
||||
const xx1 = (x1 - x2) / (2 * w) - (y1 - y2) / (2 * h);
|
||||
const yy1 = (x1 - x2) / (2 * w) + (y1 - y2) / (2 * h);
|
||||
const a = 1 / (Math.abs(xx1) + Math.abs(yy1));
|
||||
const xx3 = a * xx1;
|
||||
const yy3 = a * yy1;
|
||||
const x = w * (xx3 + yy3) + x2;
|
||||
const y = h * (-xx3 + yy3) + y2;
|
||||
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
// returns the position (top,right,bottom or right) passed node compared to the intersection point
|
||||
function getEdgePosition(node, intersectionPoint) {
|
||||
const n = { ...node.internals.positionAbsolute, ...node };
|
||||
const nx = Math.round(n.x);
|
||||
const ny = Math.round(n.y);
|
||||
const px = Math.round(intersectionPoint.x);
|
||||
const py = Math.round(intersectionPoint.y);
|
||||
|
||||
if (px <= nx + 1) {
|
||||
return Position.Left;
|
||||
}
|
||||
if (px >= nx + n.measured.width - 1) {
|
||||
return Position.Right;
|
||||
}
|
||||
if (py <= ny + 1) {
|
||||
return Position.Top;
|
||||
}
|
||||
if (py >= n.y + n.measured.height - 1) {
|
||||
return Position.Bottom;
|
||||
}
|
||||
|
||||
return Position.Top;
|
||||
}
|
||||
|
||||
// returns the parameters (sx, sy, tx, ty, sourcePos, targetPos) you need to create an edge
|
||||
export function getEdgeParams(source, target) {
|
||||
const sourceIntersectionPoint = getNodeIntersection(source, target);
|
||||
const targetIntersectionPoint = getNodeIntersection(target, source);
|
||||
|
||||
const sourcePos = getEdgePosition(source, sourceIntersectionPoint);
|
||||
const targetPos = getEdgePosition(target, targetIntersectionPoint);
|
||||
|
||||
return {
|
||||
sx: sourceIntersectionPoint.x,
|
||||
sy: sourceIntersectionPoint.y,
|
||||
tx: targetIntersectionPoint.x,
|
||||
ty: targetIntersectionPoint.y,
|
||||
sourcePos,
|
||||
targetPos,
|
||||
};
|
||||
}
|
||||
|
||||
export function createNodesAndEdges() {
|
||||
const nodes = [];
|
||||
const edges = [];
|
||||
const center = { x: window.innerWidth / 2, y: window.innerHeight / 2 };
|
||||
|
||||
nodes.push({ id: "target", data: { label: "Target" }, position: center });
|
||||
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const degrees = i * (360 / 8);
|
||||
const radians = degrees * (Math.PI / 180);
|
||||
const x = 250 * Math.cos(radians) + center.x;
|
||||
const y = 250 * Math.sin(radians) + center.y;
|
||||
|
||||
nodes.push({ id: `${i}`, data: { label: "Source" }, position: { x, y } });
|
||||
|
||||
edges.push({
|
||||
id: `edge-${i}`,
|
||||
target: "target",
|
||||
source: `${i}`,
|
||||
type: "floating",
|
||||
markerEnd: {
|
||||
type: MarkerType.Arrow,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return { nodes, edges };
|
||||
}
|
||||
196
cofounder/dashboard/src/components/flow/helpers/zTouchEdit.js
Normal file
196
cofounder/dashboard/src/components/flow/helpers/zTouchEdit.js
Normal file
@@ -0,0 +1,196 @@
|
||||
export default {
|
||||
'code[class*="language-"]': {
|
||||
color: "white",
|
||||
fontFamily: "JetBrains Mono",
|
||||
textAlign: "left",
|
||||
whiteSpace: "pre",
|
||||
wordSpacing: "normal",
|
||||
wordBreak: "normal",
|
||||
wordWrap: "normal",
|
||||
MozTabSize: "4",
|
||||
OTabSize: "4",
|
||||
tabSize: "4",
|
||||
WebkitHyphens: "none",
|
||||
MozHyphens: "none",
|
||||
msHyphens: "none",
|
||||
hyphens: "none",
|
||||
lineHeight: "25px",
|
||||
fontSize: "0.8rem",
|
||||
margin: "5px 0",
|
||||
},
|
||||
'pre[class*="language-"]': {
|
||||
color: "white",
|
||||
fontFamily: "JetBrains Mono",
|
||||
textAlign: "left",
|
||||
whiteSpace: "pre",
|
||||
wordSpacing: "normal",
|
||||
wordBreak: "normal",
|
||||
wordWrap: "normal",
|
||||
MozTabSize: "4",
|
||||
OTabSize: "4",
|
||||
tabSize: "4",
|
||||
WebkitHyphens: "none",
|
||||
MozHyphens: "none",
|
||||
msHyphens: "none",
|
||||
hyphens: "none",
|
||||
lineHeight: "25px",
|
||||
fontSize: "0.85rem",
|
||||
margin: "0.5em 0",
|
||||
background: "#2a2a2a",
|
||||
padding: "1em",
|
||||
overflow: "auto",
|
||||
},
|
||||
'pre[class*="language-"] *': {
|
||||
fontFamily: "JetBrains Mono",
|
||||
},
|
||||
':not(pre) > code[class*="language-"]': {
|
||||
color: "white",
|
||||
background: "#0a143c",
|
||||
padding: "0.1em",
|
||||
borderRadius: "0.3em",
|
||||
whiteSpace: "normal",
|
||||
},
|
||||
'pre[class*="language-"]::-moz-selection': {
|
||||
textShadow: "none",
|
||||
background: "rgba(29, 59, 83, 0.99)",
|
||||
},
|
||||
'pre[class*="language-"] ::-moz-selection': {
|
||||
textShadow: "none",
|
||||
background: "rgba(29, 59, 83, 0.99)",
|
||||
},
|
||||
'code[class*="language-"]::-moz-selection': {
|
||||
textShadow: "none",
|
||||
background: "rgba(29, 59, 83, 0.99)",
|
||||
},
|
||||
'code[class*="language-"] ::-moz-selection': {
|
||||
textShadow: "none",
|
||||
background: "rgba(29, 59, 83, 0.99)",
|
||||
},
|
||||
'pre[class*="language-"]::selection': {
|
||||
textShadow: "none",
|
||||
background: "rgba(29, 59, 83, 0.99)",
|
||||
},
|
||||
'pre[class*="language-"] ::selection': {
|
||||
textShadow: "none",
|
||||
background: "rgba(29, 59, 83, 0.99)",
|
||||
},
|
||||
'code[class*="language-"]::selection': {
|
||||
textShadow: "none",
|
||||
background: "rgba(29, 59, 83, 0.99)",
|
||||
},
|
||||
'code[class*="language-"] ::selection': {
|
||||
textShadow: "none",
|
||||
background: "rgba(29, 59, 83, 0.99)",
|
||||
},
|
||||
comment: {
|
||||
color: "#6272a4",
|
||||
fontStyle: "italic",
|
||||
},
|
||||
prolog: {
|
||||
color: "#6272a4",
|
||||
fontStyle: "italic",
|
||||
},
|
||||
cdata: {
|
||||
color: "#6272a4",
|
||||
fontStyle: "italic",
|
||||
},
|
||||
punctuation: {
|
||||
color: "#f8f8f2",
|
||||
},
|
||||
".namespace": {
|
||||
color: "#f8f8f2",
|
||||
},
|
||||
deleted: {
|
||||
color: "#ff5555",
|
||||
fontStyle: "italic",
|
||||
},
|
||||
symbol: {
|
||||
color: "#ff79c6",
|
||||
},
|
||||
property: {
|
||||
color: "#ff79c6",
|
||||
},
|
||||
tag: {
|
||||
color: "#ff79c6",
|
||||
},
|
||||
operator: {
|
||||
color: "#ff79c6",
|
||||
},
|
||||
keyword: {
|
||||
color: "#ff79c6",
|
||||
},
|
||||
boolean: {
|
||||
color: "#bd93f9",
|
||||
},
|
||||
number: {
|
||||
color: "#bd93f9",
|
||||
},
|
||||
constant: {
|
||||
color: "#8be9fd",
|
||||
},
|
||||
function: {
|
||||
color: "#8be9fd",
|
||||
},
|
||||
builtin: {
|
||||
color: "#8be9fd",
|
||||
},
|
||||
char: {
|
||||
color: "#8be9fd",
|
||||
},
|
||||
selector: {
|
||||
color: "#ff79c6",
|
||||
fontStyle: "italic",
|
||||
},
|
||||
doctype: {
|
||||
color: "#6272a4",
|
||||
fontStyle: "italic",
|
||||
},
|
||||
"attr-name": {
|
||||
color: "#50fa7b",
|
||||
fontStyle: "italic",
|
||||
},
|
||||
inserted: {
|
||||
color: "#50fa7b",
|
||||
fontStyle: "italic",
|
||||
},
|
||||
string: {
|
||||
color: "#f1fa8c",
|
||||
},
|
||||
url: {
|
||||
color: "#f1fa8c",
|
||||
},
|
||||
entity: {
|
||||
color: "#f1fa8c",
|
||||
},
|
||||
".language-css .token.string": {
|
||||
color: "#f1fa8c",
|
||||
},
|
||||
".style .token.string": {
|
||||
color: "#f1fa8c",
|
||||
},
|
||||
"class-name": {
|
||||
color: "#8be9fd",
|
||||
},
|
||||
atrule: {
|
||||
color: "#50fa7b",
|
||||
},
|
||||
"attr-value": {
|
||||
color: "#f1fa8c",
|
||||
},
|
||||
regex: {
|
||||
color: "#ffb86c",
|
||||
},
|
||||
important: {
|
||||
color: "#ffb86c",
|
||||
fontWeight: "bold",
|
||||
},
|
||||
variable: {
|
||||
color: "#f8f8f2",
|
||||
},
|
||||
bold: {
|
||||
fontWeight: "bold",
|
||||
},
|
||||
italic: {
|
||||
fontStyle: "italic",
|
||||
},
|
||||
};
|
||||
97
cofounder/dashboard/src/components/flow/keymap.tsx
Normal file
97
cofounder/dashboard/src/components/flow/keymap.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
export default {
|
||||
meta: {
|
||||
"pm.details": {
|
||||
type: "pm",
|
||||
name: "Details",
|
||||
desc: "User-submitted Project Details",
|
||||
},
|
||||
|
||||
"pm.prd": { type: "pm", name: "PRD", desc: "Product Requirements Document" },
|
||||
"pm.frd": { type: "pm", name: "FRD", desc: "Features Requirements Document" },
|
||||
"pm.drd": { type: "pm", name: "DRD", desc: "Database Requirements Document" },
|
||||
"pm.brd": { type: "pm", name: "BRD", desc: "Backend Requirements Document" },
|
||||
"pm.uxsmd": { type: "pm", name: "UXSMD", desc: "UX Sitemap Document" },
|
||||
"pm.uxdmd": { type: "pm", name: "UXDMD", desc: "UX Datamap Document" },
|
||||
|
||||
"db.schemas": {
|
||||
type: "db",
|
||||
name: "DB/schemas",
|
||||
desc: "Database Tables Schemas",
|
||||
},
|
||||
"db.postgres": {
|
||||
type: "db",
|
||||
name: "DB/postgres",
|
||||
desc: "Database Postgresql Commands",
|
||||
},
|
||||
|
||||
"backend.specifications.openapi": {
|
||||
type: "backend",
|
||||
name: "backend/define:openapi",
|
||||
desc: "Backend Definition : openAPI",
|
||||
},
|
||||
"backend.specifications.asyncapi": {
|
||||
type: "backend",
|
||||
name: "backend/define:asyncapi",
|
||||
desc: "Backend Definition : asyncAPI",
|
||||
},
|
||||
|
||||
"backend.server.main": {
|
||||
type: "backend",
|
||||
name: "backend/server:main",
|
||||
desc: "Backend Server : Main",
|
||||
},
|
||||
|
||||
"uxsitemap.structure": {
|
||||
type: "ux",
|
||||
name: "ux/sitemap:structure",
|
||||
desc: "UX Sitemap",
|
||||
},
|
||||
"uxdatamap.structure": {
|
||||
type: "ux",
|
||||
name: "ux/datamap:structure",
|
||||
desc: "UX Datamap Structure",
|
||||
},
|
||||
"uxdatamap.views": {
|
||||
type: "ux",
|
||||
name: "ux/datamap:views",
|
||||
desc: "UX Datamap Views",
|
||||
},
|
||||
|
||||
"webapp.react.root": {
|
||||
type: "webapp-structure",
|
||||
name: "webapp/react:root",
|
||||
desc: "Webapp App Root Component",
|
||||
},
|
||||
"webapp.react.store": {
|
||||
type: "webapp-structure",
|
||||
name: "webapp/react:store",
|
||||
desc: "Webapp Data Store",
|
||||
},
|
||||
"webapp.react.views": {
|
||||
type: "webapp-view",
|
||||
name: "webapp/react:views",
|
||||
desc: "Webapp View",
|
||||
},
|
||||
"settings.config.package": {
|
||||
type: "ux",
|
||||
name: "settings/config:package",
|
||||
desc: "Dependencies & .env for package.json",
|
||||
},
|
||||
"settings.preferences.versions": {
|
||||
type: "ux",
|
||||
name: "settings/preferences:versions",
|
||||
desc: "Components versions preferences",
|
||||
},
|
||||
},
|
||||
types: {
|
||||
"pm.details": "yaml", // is kinda ignored , hardcoded fix in "@/components/flow/nodes/cofounder-node"
|
||||
pm: "markdown",
|
||||
db: "yaml",
|
||||
backend: "complex",
|
||||
uxsitemap: "yaml",
|
||||
uxdatamap: "yaml",
|
||||
"webapp-structure": "complex",
|
||||
"webapp-view": "complex",
|
||||
settings: "yaml",
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
.react-flow__node-cofounder_iframe {
|
||||
background: #000000;
|
||||
border: 1px solid #555;
|
||||
border-radius: 5px;
|
||||
text-align: left;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
.react-flow__node-cofounder_node {
|
||||
background: #000000;
|
||||
border: 1px solid #555;
|
||||
border-radius: 5px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.markdown {
|
||||
color: #ffffff; /* Text color for dark mode */
|
||||
font-size: 0.975rem; /* 50% of typical size */
|
||||
line-height: 1.3; /* Adjusted spacing between lines */
|
||||
padding: 0.3rem; /* Reduced padding around the markdown container */
|
||||
border-bottom: 1px solid #555; /* Separator border for sections */
|
||||
margin-bottom: 1rem; /* Space below the markdown container */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
.markdown h1 {
|
||||
font-size: 1.5rem; /* 50% of typical h1 size */
|
||||
margin: 0.3rem 0; /* Reduced margin above and below */
|
||||
border-bottom: 1px solid #444; /* Separator border below h1 */
|
||||
padding-bottom: 0.2rem; /* Padding below h1 */
|
||||
}
|
||||
|
||||
.markdown h2 {
|
||||
font-size: 1.25rem; /* 50% of typical h2 size */
|
||||
margin: 0.3rem 0; /* Reduced margin above and below */
|
||||
border-bottom: 1px solid #444; /* Separator border below h2 */
|
||||
padding-bottom: 0.2rem; /* Padding below h2 */
|
||||
}
|
||||
|
||||
.markdown h3 {
|
||||
font-size: 1rem; /* 50% of typical h3 size */
|
||||
margin: 0.2rem 0; /* Reduced margin above and below */
|
||||
border-bottom: 1px solid #444; /* Separator border below h3 */
|
||||
padding-bottom: 0.2rem; /* Padding below h3 */
|
||||
}
|
||||
|
||||
.markdown p {
|
||||
margin: 0.3rem 0; /* Reduced margin above and below paragraphs */
|
||||
line-height: 1.75;
|
||||
/* Added line spacing for better readability */
|
||||
}
|
||||
|
||||
.markdown ul,
|
||||
.markdown ol {
|
||||
margin: 0.3rem 0; /* Reduced margin above and below lists */
|
||||
padding-left: 1.2rem; /* Slightly reduced indentation for lists */
|
||||
}
|
||||
|
||||
.markdown li {
|
||||
margin: 0.1rem 0; /* Reduced margin above and below list items */
|
||||
border-bottom: 1px solid #222;
|
||||
}
|
||||
|
||||
.markdown blockquote {
|
||||
border-left: 2px solid #555; /* Border for blockquotes */
|
||||
padding-left: 0.5rem; /* Padding for blockquote text */
|
||||
color: #cccccc; /* Lighter text color for blockquotes */
|
||||
margin: 0.3rem 0; /* Reduced margin above and below blockquotes */
|
||||
border-bottom: 1px solid #222;
|
||||
}
|
||||
|
||||
.markdown code {
|
||||
background-color: #222; /* Background for inline code */
|
||||
color: #f8f8f2; /* Text color for inline code */
|
||||
padding: 0.2rem 0.4rem; /* Padding for inline code */
|
||||
margin: 0.1rem; /* Little margin for inline code */
|
||||
border-radius: 3px; /* Rounded corners for inline code */
|
||||
}
|
||||
|
||||
.markdown pre {
|
||||
background-color: #111; /* Background for code blocks */
|
||||
color: #f8f8f2; /* Text color for code blocks */
|
||||
padding: 0.4rem; /* Reduced padding for code blocks */
|
||||
border-radius: 5px; /* Rounded corners for code blocks */
|
||||
overflow: auto; /* Allow scrolling for long code blocks */
|
||||
margin: 0.3rem 0; /* Reduced margin above and below code blocks */
|
||||
}
|
||||
613
cofounder/dashboard/src/components/flow/nodes/cofounder-node.tsx
Normal file
613
cofounder/dashboard/src/components/flow/nodes/cofounder-node.tsx
Normal file
@@ -0,0 +1,613 @@
|
||||
import React, { memo, useState, useEffect, useRef } from "react";
|
||||
import { Handle, Position } from "@xyflow/react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import yaml from "yaml";
|
||||
import colormap from "colormap";
|
||||
|
||||
import Markdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
DialogClose,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
||||
import { PrismLight as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import yaml_syntax from "react-syntax-highlighter/dist/esm/languages/prism/yaml";
|
||||
import js_syntax from "react-syntax-highlighter/dist/esm/languages/prism/javascript";
|
||||
import sql_syntax from "react-syntax-highlighter/dist/esm/languages/prism/sql";
|
||||
import typescript_syntax from "react-syntax-highlighter/dist/esm/languages/prism/typescript";
|
||||
|
||||
import {
|
||||
dracula,
|
||||
duotoneDark,
|
||||
vscDarkPlus,
|
||||
zTouch,
|
||||
} from "react-syntax-highlighter/dist/esm/styles/prism";
|
||||
|
||||
import zTouchEdit from "@/components/flow/helpers/zTouchEdit.js";
|
||||
|
||||
export default memo(({ data, isConnectable }) => {
|
||||
const color_map = {
|
||||
pm: "#FFA500",
|
||||
db: "#000080",
|
||||
backend: "#FF10F0",
|
||||
ux: "#A020F0",
|
||||
"webapp-structure": "#39FF14",
|
||||
"webapp-view": "#05D9FF",
|
||||
};
|
||||
SyntaxHighlighter.registerLanguage("yaml", yaml_syntax);
|
||||
SyntaxHighlighter.registerLanguage("js", js_syntax);
|
||||
SyntaxHighlighter.registerLanguage("sql", sql_syntax);
|
||||
SyntaxHighlighter.registerLanguage("typescript", typescript_syntax);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const node_stream = !data.key.includes("webapp.react.views")
|
||||
? useSelector((state: any) => state.project.streamEvents[data.key])
|
||||
: useSelector(
|
||||
(state: any) =>
|
||||
state.project.streamEvents[`${data.key.split(".").slice(0, 4).join(".")}`],
|
||||
);
|
||||
|
||||
const node_data = useSelector(
|
||||
(state: any) => state.project.projectData[data.key],
|
||||
);
|
||||
let node_extra;
|
||||
if (data.key.includes("webapp.react.views")) {
|
||||
node_extra = useSelector(
|
||||
(state: any) =>
|
||||
state.project.projectData[data.key.replace(".react.", ".layout.")],
|
||||
);
|
||||
}
|
||||
|
||||
const streamContainerRef = useRef<HTMLDivElement>(null);
|
||||
const [metaHeaderClass, setMetaHeaderClass] = useState("");
|
||||
const [refresh, setRefresh] = useState(Date.now());
|
||||
|
||||
function getColor() {
|
||||
return data?.meta?.type && color_map[data.meta.type]
|
||||
? `[${color_map[data.meta.type]}]`
|
||||
: "none";
|
||||
}
|
||||
function getLanguage() {
|
||||
if (data.key === "db.postgres") return "sql";
|
||||
return "yaml";
|
||||
}
|
||||
function getContent() {
|
||||
if (!node_data) return "";
|
||||
if (data.key === "db.postgres") return node_data;
|
||||
return yaml.stringify(node_data);
|
||||
}
|
||||
|
||||
const [selectedVersion, setSelectedVersion] = useState<string | boolean>(
|
||||
false,
|
||||
);
|
||||
useEffect(() => {
|
||||
if (data.key.includes("webapp.")) {
|
||||
if (node_data && Object.keys(node_data).length) {
|
||||
setSelectedVersion(Object.keys(node_data).reverse()[0]);
|
||||
}
|
||||
}
|
||||
}, [node_data]);
|
||||
|
||||
/*
|
||||
useEffect(() => {
|
||||
setRefresh(Date.now())
|
||||
}, [node_data , node_stream , node_extra]);
|
||||
*/
|
||||
|
||||
function getMinifiedContent() {
|
||||
// webapp component with versionning case
|
||||
if (
|
||||
data.key.includes("webapp.react.root") ||
|
||||
data.key.includes("webapp.react.store")
|
||||
) {
|
||||
return (
|
||||
<div className={`grid grid-cols-12`}>
|
||||
<div className="col-span-9 grid grid-cols-2">
|
||||
{(node_stream?.is_running && node_stream?.data && (
|
||||
<pre
|
||||
className="p-2 m-2 max-h-[40vh] overflow-auto whitespace-pre-wrap break-words"
|
||||
style={{ fontFamily: "JetBrains Mono", fontWeight: 400 }}
|
||||
>
|
||||
<div className={`mx-2`}>
|
||||
<div className="flex justify-center items-center p-4 m-4">
|
||||
<div className="animate-spin rounded-full h-5 w-5 border-r border-[#aaa]"></div>
|
||||
</div>
|
||||
</div>
|
||||
</pre>
|
||||
)) || <></>}
|
||||
|
||||
<pre
|
||||
ref={streamContainerRef}
|
||||
className={`p-2 m-2 max-h-[40vh] overflow-auto whitespace-pre-wrap break-words duration-200
|
||||
${node_stream?.is_running && node_stream?.data ? "" : "col-span-2"}`}
|
||||
style={{ fontFamily: "JetBrains Mono", fontWeight: 400 }}
|
||||
>
|
||||
{(node_stream?.is_running && node_stream?.data && (
|
||||
<>{node_stream.data.data}</>
|
||||
)) || (
|
||||
<>
|
||||
{selectedVersion && node_data
|
||||
? node_data[selectedVersion].tsx.slice(0, 300) + " ..."
|
||||
: ""}
|
||||
</>
|
||||
)}
|
||||
</pre>
|
||||
</div>
|
||||
{(node_stream?.is_running && node_stream?.data && (
|
||||
<>
|
||||
<div className="col-span-3 text-sm p-2 m-2">
|
||||
<h3 className="py-2 mb-2 opacity-50">processing</h3>
|
||||
</div>
|
||||
</>
|
||||
)) || (
|
||||
<>
|
||||
{(node_data && (
|
||||
<div className="col-span-3 text-sm p-2 m-2">
|
||||
<h3 className="border-b py-2 mb-2 border-[#333]">versions</h3>
|
||||
<div className="grid">
|
||||
{Object.entries(node_data)
|
||||
.reverse()
|
||||
.map(([version, item], index) => (
|
||||
<a
|
||||
key={index}
|
||||
className={`rounded cursor-pointer p-2 mb-1 hover:bg-[#222] duration-200 ${
|
||||
selectedVersion === version ? "bg-[#333]" : ""
|
||||
} `}
|
||||
onClick={() => {
|
||||
setSelectedVersion(version);
|
||||
}}
|
||||
>
|
||||
{version}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)) ||
|
||||
""}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// webapp view with layout and versionning case
|
||||
if (data.key.includes("webapp.react.views")) {
|
||||
return (
|
||||
<div className={`grid grid-cols-12`}>
|
||||
<div className={`col-span-9 grid grid-cols-2`}>
|
||||
{(node_stream?.is_running && node_stream?.data && (
|
||||
<pre
|
||||
className="p-2 m-2 max-h-[40vh] overflow-auto whitespace-pre-wrap break-words"
|
||||
style={{ fontFamily: "JetBrains Mono", fontWeight: 400 }}
|
||||
>
|
||||
<div className={`mx-2`}>
|
||||
<div className="flex justify-center items-center p-4 m-4">
|
||||
<div className="animate-spin rounded-full h-5 w-5 border-r border-[#aaa]"></div>
|
||||
</div>
|
||||
</div>
|
||||
</pre>
|
||||
)) || <></>}
|
||||
|
||||
<pre
|
||||
ref={streamContainerRef}
|
||||
className={`p-2 m-2 max-h-[40vh] overflow-auto whitespace-pre-wrap break-words duration-200
|
||||
${node_stream?.is_running && node_stream?.data ? "" : "col-span-2"}
|
||||
text-xs
|
||||
`}
|
||||
style={{ fontFamily: "JetBrains Mono", fontWeight: 400 }}
|
||||
>
|
||||
{(node_stream?.is_running && node_stream?.data && (
|
||||
<>{node_stream.data.data}</>
|
||||
)) || (
|
||||
<>
|
||||
{selectedVersion && node_data
|
||||
? node_data[selectedVersion].tsx.slice(0, 300) + " ..."
|
||||
: ""}
|
||||
</>
|
||||
)}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
{(node_stream?.is_running && node_stream?.data && (
|
||||
<>
|
||||
<div className="col-span-3 text-sm p-2 m-2">
|
||||
<h3 className="py-2 mb-2 opacity-50">processing</h3>
|
||||
</div>
|
||||
</>
|
||||
)) || (
|
||||
<>
|
||||
<div className="col-span-3 text-sm p-2 m-2 max-h-[40vh] overflow-auto">
|
||||
<h3 className="border-b py-2 mb-2 border-[#333]">versions</h3>
|
||||
<div className="grid">
|
||||
{(node_data &&
|
||||
Object.entries(node_data)
|
||||
.reverse()
|
||||
.map(([version, item], index) => (
|
||||
<a
|
||||
key={index}
|
||||
className={`rounded cursor-pointer p-2 mb-1 hover:bg-[#222] duration-200 ${
|
||||
selectedVersion === version ? "bg-[#333]" : ""
|
||||
} `}
|
||||
onClick={() => {
|
||||
setSelectedVersion(version);
|
||||
}}
|
||||
>
|
||||
{version}
|
||||
</a>
|
||||
))) || <></>}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{(node_extra &&
|
||||
selectedVersion &&
|
||||
node_extra[selectedVersion] &&
|
||||
(node_extra[selectedVersion]?.render?.image?.url?.length ||
|
||||
node_extra[selectedVersion]?.render?.image?.local?.length) && (
|
||||
<div className="col-span-12 p-2 m-2 text-xs">
|
||||
<img
|
||||
title={`Design mockup reference generated for ${data.key}, version : ${selectedVersion}`}
|
||||
alt={`Design mockup reference generated for ${data.key}, version : ${selectedVersion}`}
|
||||
src={
|
||||
node_extra[selectedVersion]?.render?.image?.url?.length
|
||||
? node_extra[selectedVersion]?.render?.image?.url
|
||||
: `http://localhost:667/storage/${node_extra[selectedVersion]?.render?.image?.local.split("/storage/")[1]}`
|
||||
}
|
||||
></img>
|
||||
</div>
|
||||
)) || <></>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// default case
|
||||
return (
|
||||
<div
|
||||
className={`grid ${node_stream?.is_running && node_stream?.data ? "grid-cols-2" : ""}`}
|
||||
>
|
||||
<div>
|
||||
<pre
|
||||
className="p-2 m-2 max-h-[25vh] overflow-auto whitespace-pre-wrap break-words"
|
||||
style={{ fontFamily: "JetBrains Mono", fontWeight: 400 }}
|
||||
>
|
||||
{(node_stream?.is_running && node_stream?.data && (
|
||||
<>
|
||||
<div className="flex justify-center items-center p-4 m-4">
|
||||
<div className="animate-spin rounded-full h-5 w-5 border-r border-[#aaa]"></div>
|
||||
</div>
|
||||
</>
|
||||
)) || <></>}
|
||||
{(node_data && (
|
||||
<>
|
||||
{typeof node_data === "string"
|
||||
? node_data.slice(0, 100) + " ..."
|
||||
: yaml.stringify(node_data).slice(0, 100) + " ..."}
|
||||
</>
|
||||
)) || <></>}
|
||||
</pre>
|
||||
</div>
|
||||
{(node_stream?.is_running && node_stream?.data && (
|
||||
<div>
|
||||
<pre
|
||||
ref={streamContainerRef}
|
||||
className="p-2 m-2 max-h-[25vh] overflow-auto whitespace-pre-wrap break-words font-light"
|
||||
>
|
||||
{node_stream.data.data}
|
||||
</pre>
|
||||
</div>
|
||||
)) || <></>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function getExpandedContent() {
|
||||
// webapp root/store case
|
||||
if (
|
||||
data.key.includes("webapp.react.root") ||
|
||||
data.key.includes("webapp.react.store")
|
||||
) {
|
||||
if (!selectedVersion) return <></>;
|
||||
return (
|
||||
<>
|
||||
{(selectedVersion && node_data && (
|
||||
<pre className="rounded grid grid-cols-6 gap-2 rounded-lg p-2 text-white text-xs overflow-auto whitespace-pre-wrap break-words">
|
||||
<SyntaxHighlighter
|
||||
className="col-span-4 rounded bg-black text-xs"
|
||||
language={"typescript"}
|
||||
style={zTouchEdit}
|
||||
wrapLines={true}
|
||||
wrapLongLines={true}
|
||||
>
|
||||
{node_data[selectedVersion].tsx}
|
||||
</SyntaxHighlighter>
|
||||
<SyntaxHighlighter
|
||||
className="col-span-2 rounded bg-black text-xs"
|
||||
language={getLanguage()}
|
||||
style={zTouchEdit}
|
||||
wrapLines={true}
|
||||
wrapLongLines={true}
|
||||
>
|
||||
{yaml.stringify({
|
||||
...(node_data[selectedVersion]?.dependencies
|
||||
? { dependencies: node_data[selectedVersion].dependencies }
|
||||
: {}),
|
||||
...(node_data[selectedVersion]?.analysis
|
||||
? { analysis: node_data[selectedVersion].analysis }
|
||||
: {}),
|
||||
})}
|
||||
</SyntaxHighlighter>
|
||||
</pre>
|
||||
)) ||
|
||||
""}
|
||||
</>
|
||||
);
|
||||
}
|
||||
// webapp view case
|
||||
if (data.key.includes("webapp.react.views")) {
|
||||
if (!selectedVersion) return <></>;
|
||||
return (
|
||||
<>
|
||||
{(selectedVersion && node_data && (
|
||||
<pre className="rounded grid grid-cols-6 gap-2 rounded-lg p-2 text-white text-xs overflow-auto whitespace-pre-wrap break-words">
|
||||
<SyntaxHighlighter
|
||||
className="col-span-4 rounded bg-black text-xs"
|
||||
language={"typescript"}
|
||||
style={zTouchEdit}
|
||||
wrapLines={true}
|
||||
wrapLongLines={true}
|
||||
>
|
||||
{node_data[selectedVersion].tsx}
|
||||
</SyntaxHighlighter>
|
||||
<SyntaxHighlighter
|
||||
className="col-span-2 rounded bg-black text-xs"
|
||||
language={getLanguage()}
|
||||
style={zTouchEdit}
|
||||
wrapLines={true}
|
||||
wrapLongLines={true}
|
||||
>
|
||||
{yaml.stringify({
|
||||
...(node_data[selectedVersion]?.dependencies
|
||||
? { dependencies: node_data[selectedVersion].dependencies }
|
||||
: {}),
|
||||
...(node_data[selectedVersion]?.analysis
|
||||
? { analysis: node_data[selectedVersion].analysis }
|
||||
: {}),
|
||||
})}
|
||||
</SyntaxHighlighter>
|
||||
</pre>
|
||||
)) ||
|
||||
""}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// server case
|
||||
if (data.key === "backend.server.main") {
|
||||
return (
|
||||
<>
|
||||
{(node_data && (
|
||||
<pre className="rounded grid grid-cols-6 gap-2 rounded-lg p-2 text-white text-xs overflow-auto whitespace-pre-wrap break-words">
|
||||
<SyntaxHighlighter
|
||||
className="col-span-4 rounded bg-black text-xs"
|
||||
language={"javascript"}
|
||||
style={zTouchEdit}
|
||||
wrapLines={true}
|
||||
wrapLongLines={true}
|
||||
>
|
||||
{node_data.mjs}
|
||||
</SyntaxHighlighter>
|
||||
<SyntaxHighlighter
|
||||
className="col-span-2 rounded bg-black text-xs"
|
||||
language={getLanguage()}
|
||||
style={zTouchEdit}
|
||||
wrapLines={true}
|
||||
wrapLongLines={true}
|
||||
>
|
||||
{yaml.stringify({
|
||||
env: node_data.env,
|
||||
dependencies: node_data.dependencies,
|
||||
})}
|
||||
</SyntaxHighlighter>
|
||||
</pre>
|
||||
)) ||
|
||||
""}
|
||||
</>
|
||||
);
|
||||
}
|
||||
// default
|
||||
return (
|
||||
<>
|
||||
<pre className="rounded rounded-lg p-2 text-white text-xs overflow-auto whitespace-pre-wrap break-words">
|
||||
<SyntaxHighlighter
|
||||
className="rounded bg-black text-xs"
|
||||
language={getLanguage()}
|
||||
style={zTouchEdit}
|
||||
wrapLines={true}
|
||||
wrapLongLines={true}
|
||||
>
|
||||
{getContent()}
|
||||
</SyntaxHighlighter>
|
||||
</pre>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (streamContainerRef.current) {
|
||||
streamContainerRef.current.scrollTop =
|
||||
streamContainerRef.current.scrollHeight;
|
||||
}
|
||||
}, [node_stream]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="hidden bg-[#FFA500] bg-[#000080] bg-[#FF10F0] bg-[#A020F0] bg-[#05D9FF] bg-[#39FF14]">
|
||||
_tw_manual_debugging_ _preload_all_colors_variations_so_it_builds_them
|
||||
</div>
|
||||
<div
|
||||
className={`dark bg-[#0a0a0a] font-light ${node_stream?.is_running && node_stream?.data ? "max-w-[60vw] xl:max-w-[50vw]" : "max-w-[60vw] xl:max-w-[40vw]"} overflow-auto text-xs`}
|
||||
key={refresh}
|
||||
>
|
||||
<div className="opacity-0">
|
||||
<Handle
|
||||
type="target"
|
||||
id="top"
|
||||
position={Position.Top}
|
||||
style={{ background: "#555" }}
|
||||
onConnect={(params) => console.log("handle onConnect", params)}
|
||||
isConnectable={isConnectable}
|
||||
/>
|
||||
<Handle
|
||||
type="target"
|
||||
id="botom"
|
||||
position={Position.Bottom}
|
||||
style={{ background: "#555" }}
|
||||
onConnect={(params) => console.log("handle onConnect", params)}
|
||||
isConnectable={isConnectable}
|
||||
/>
|
||||
<Handle
|
||||
type="target"
|
||||
id="left"
|
||||
position={Position.Left}
|
||||
style={{ background: "#555" }}
|
||||
onConnect={(params) => console.log("handle onConnect", params)}
|
||||
isConnectable={isConnectable}
|
||||
/>
|
||||
<Handle
|
||||
type="target"
|
||||
id="right"
|
||||
position={Position.Right}
|
||||
style={{ background: "#555" }}
|
||||
onConnect={(params) => console.log("handle onConnect", params)}
|
||||
isConnectable={isConnectable}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="text-white rounded rounded-xl p-2 font-light">
|
||||
<div
|
||||
key={refresh}
|
||||
className="text-base p-2 m-2 duration-200 rounded flex gap-2 items-start"
|
||||
>
|
||||
<div className={`h-8 w-8 m-2 rounded bg-${getColor()}`}></div>
|
||||
<div>
|
||||
<strong>{data.meta.name}</strong>
|
||||
<br />
|
||||
<span className="opacity-80">
|
||||
{data.meta.desc}{" "}
|
||||
{!data.key.includes("webapp.react.views")
|
||||
? ""
|
||||
: ` : ${data.key.split(".")[3]}`}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{getMinifiedContent()}
|
||||
|
||||
{(node_data && (
|
||||
<Dialog>
|
||||
<div className="flex justify-end border-t pt-2 my-2 border-[#222]">
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline">View</Button>
|
||||
</DialogTrigger>
|
||||
</div>
|
||||
<DialogContent className="font-light bg-white/10 backdrop-blur-md border-[#222] max-w-[90vw] h-[90vh] max-h-[90vh] overflow-auto p-8">
|
||||
<DialogHeader className="text-[#aaa]">
|
||||
<DialogTitle>Detailed View</DialogTitle>
|
||||
<DialogDescription className="text-white text-lg whitespace-pre-wrap break-words font-light">
|
||||
{data.key}{" "}
|
||||
<strong className="text-base">
|
||||
{selectedVersion ? `{ ${selectedVersion} }` : ""}
|
||||
</strong>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{(data.meta.content_type === "markdown" && data.key != "pm.details" && (
|
||||
<>
|
||||
<div className="rounded rounded-lg p-12 m-2 bg-[#2a2a2a] text-white text-sm overflow-auto whitespace-pre-wrap break-words">
|
||||
<Markdown className="markdown" remarkPlugins={[remarkGfm]}>
|
||||
{node_data}
|
||||
</Markdown>
|
||||
</div>
|
||||
</>
|
||||
)) ||
|
||||
getExpandedContent()}
|
||||
|
||||
<pre
|
||||
className=""
|
||||
style={{ fontFamily: "JetBrains Mono", fontWeight: 400 }}
|
||||
>
|
||||
{/*<SyntaxHighlighter language="yaml" style={vscDarkPlus} wrapLines={true} wrapLongLines={true} >
|
||||
{yaml.stringify(node_data)}
|
||||
</SyntaxHighlighter>
|
||||
*/}
|
||||
</pre>
|
||||
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button variant="">Back</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)) || <></>}
|
||||
|
||||
{/*
|
||||
<input
|
||||
className="nodrag rounded-lg p-1" style={{backgroundColor:bgColor}}
|
||||
type="color"
|
||||
onChange={(event) => {
|
||||
const newColor = event.target.value;
|
||||
setBgColor(newColor);
|
||||
data.onChange(event);
|
||||
}}
|
||||
defaultValue={data.color}
|
||||
style={{ cursor: 'pointer', width: '50px', height: '50px' }}
|
||||
/>
|
||||
*/}
|
||||
</div>
|
||||
<div className="opacity-0">
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Top}
|
||||
id="top"
|
||||
style={{ left: 10, background: "#555" }}
|
||||
isConnectable={isConnectable}
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
id="right"
|
||||
style={{ top: 10, background: "#555" }}
|
||||
isConnectable={isConnectable}
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Bottom}
|
||||
id="bottom"
|
||||
style={{ left: 10, background: "#555" }}
|
||||
isConnectable={isConnectable}
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Left}
|
||||
id="left"
|
||||
style={{ top: 10, background: "#555" }}
|
||||
isConnectable={isConnectable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
.react-flow__node-cofounder_terminal {
|
||||
background: #000000;
|
||||
border: 1px solid #555;
|
||||
border-radius: 5px;
|
||||
text-align: left;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
.react-flow__node-colorSelector {
|
||||
background: #000000;
|
||||
border: 1px solid #555;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
120
cofounder/dashboard/src/components/flow/nodes/color-selector.tsx
Normal file
120
cofounder/dashboard/src/components/flow/nodes/color-selector.tsx
Normal file
@@ -0,0 +1,120 @@
|
||||
import React, { memo, useState, useEffect } from "react";
|
||||
import { Handle, Position } from "@xyflow/react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
||||
export default memo(({ data, isConnectable }) => {
|
||||
const [bgColor, setBgColor] = useState(data.color);
|
||||
const [randomString, setRandomString] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setRandomString((prev) => {
|
||||
const newString = prev + Math.random().toString(36).charAt(2);
|
||||
if (newString.length > 500) {
|
||||
clearInterval(interval);
|
||||
return newString;
|
||||
}
|
||||
return newString;
|
||||
});
|
||||
}, 10);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="dark">
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Top}
|
||||
style={{ background: "#555" }}
|
||||
onConnect={(params) => console.log("handle onConnect", params)}
|
||||
isConnectable={isConnectable}
|
||||
/>
|
||||
<div
|
||||
className="text-white rounded rounded-xl p-2"
|
||||
style={{
|
||||
fontFamily: "JetBrains Mono",
|
||||
fontWeight: 400,
|
||||
background: bgColor,
|
||||
}}
|
||||
>
|
||||
<div className="p-2 m-2 hover:bg-[#222] duration-200">
|
||||
Custom Color Picker Node: <strong>{data.color}</strong>
|
||||
</div>
|
||||
<pre className="text-left m-4 p-4 bg-black text-white max-w-[30vw] whitespace-pre-line break-all overflow-auto">
|
||||
random str : {randomString}
|
||||
</pre>
|
||||
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline">Edit Profile</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Edit profile</DialogTitle>
|
||||
<DialogDescription>
|
||||
Make changes to your profile here. Click save when you're done.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="name" className="text-right">
|
||||
Name
|
||||
</Label>
|
||||
<Input id="name" defaultValue="Pedro Duarte" className="col-span-3" />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="username" className="text-right">
|
||||
Username
|
||||
</Label>
|
||||
<Input id="username" defaultValue="@peduarte" className="col-span-3" />
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="submit">Save changes</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/*
|
||||
<input
|
||||
className="nodrag rounded-lg p-1" style={{backgroundColor:bgColor}}
|
||||
type="color"
|
||||
onChange={(event) => {
|
||||
const newColor = event.target.value;
|
||||
setBgColor(newColor);
|
||||
data.onChange(event);
|
||||
}}
|
||||
defaultValue={data.color}
|
||||
style={{ cursor: 'pointer', width: '50px', height: '50px' }}
|
||||
/>
|
||||
*/}
|
||||
</div>
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
id="a"
|
||||
style={{ top: 10, background: "#555" }}
|
||||
isConnectable={isConnectable}
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
id="b"
|
||||
style={{ bottom: 10, top: "auto", background: "#555" }}
|
||||
isConnectable={isConnectable}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
189
cofounder/dashboard/src/components/flow/template.tsx
Normal file
189
cofounder/dashboard/src/components/flow/template.tsx
Normal file
@@ -0,0 +1,189 @@
|
||||
import { MarkerType } from "@xyflow/react";
|
||||
|
||||
const PADDING_X = 150;
|
||||
const DIST_X = PADDING_X * 2;
|
||||
const PADDING_Y = 75;
|
||||
const DIST_Y = PADDING_Y * 6;
|
||||
|
||||
export default {
|
||||
metrics: {
|
||||
PADDING_X,
|
||||
PADDING_Y,
|
||||
DIST_X,
|
||||
DIST_Y,
|
||||
},
|
||||
layers: {
|
||||
// pm layer , etc
|
||||
},
|
||||
nodes: {
|
||||
"pm.details": { position: { x: DIST_X * 2, y: -DIST_Y * 2 } },
|
||||
"pm.prd": { position: { x: 0, y: 0 } },
|
||||
"pm.frd": { position: { x: DIST_X * 4, y: 0 } },
|
||||
|
||||
"pm.drd": { position: { x: DIST_X * 4, y: DIST_Y } },
|
||||
|
||||
"db.schemas": {
|
||||
position: { x: PADDING_X * 2 + DIST_X * 5, y: DIST_Y + PADDING_Y },
|
||||
},
|
||||
"db.postgres": {
|
||||
position: { x: PADDING_X * 2 + DIST_X * 6, y: DIST_Y + PADDING_Y * 2 },
|
||||
},
|
||||
|
||||
"pm.brd": { position: { x: DIST_X * 5, y: PADDING_Y + DIST_Y * 2 } },
|
||||
"backend.specifications.openapi": {
|
||||
position: { x: DIST_X * 7, y: PADDING_Y + DIST_Y * 2 + PADDING_Y },
|
||||
},
|
||||
"backend.specifications.asyncapi": {
|
||||
position: { x: DIST_X * 7, y: PADDING_Y + DIST_Y * 2.75 + PADDING_Y },
|
||||
},
|
||||
"backend.server.main": { position: { x: DIST_X * 9, y: DIST_Y * 2.5 } },
|
||||
|
||||
"pm.uxsmd": { position: { x: 0, y: DIST_Y } },
|
||||
"pm.uxdmd": { position: { x: DIST_X * 2, y: DIST_Y } },
|
||||
|
||||
"uxsitemap.structure": {
|
||||
position: { x: -DIST_X * 3.5, y: DIST_Y * 4.5 },
|
||||
},
|
||||
"uxdatamap.structure": {
|
||||
position: { x: DIST_X * 1.5, y: DIST_Y * 2 + PADDING_Y },
|
||||
},
|
||||
"uxdatamap.views": {
|
||||
position: { x: DIST_X * 3, y: DIST_Y * 2 + PADDING_Y * 2 },
|
||||
},
|
||||
|
||||
"webapp.react.root": { position: { x: 0, y: DIST_Y * 3 + PADDING_Y * 2 } },
|
||||
"webapp.react.store": {
|
||||
position: { x: DIST_X * 3, y: DIST_Y * 3 + PADDING_Y * 2 },
|
||||
},
|
||||
"webapp.react.views": { position: { x: 0, y: DIST_Y * 6 } },
|
||||
|
||||
"settings.config.package": {
|
||||
position: { x: DIST_X * 12, y: DIST_Y * 4 },
|
||||
},
|
||||
"settings.preferences.versions": {
|
||||
position: { x: -DIST_X * 5, y: DIST_Y * 4.5 },
|
||||
},
|
||||
},
|
||||
|
||||
edges: [
|
||||
...["pm.prd", "pm.frd"].map((target) => {
|
||||
const source = "pm.details";
|
||||
return {
|
||||
id: `${source}-${target}`,
|
||||
source,
|
||||
target: target,
|
||||
};
|
||||
}),
|
||||
...["pm.frd", "pm.drd", "pm.uxsmd", "pm.uxdmd", "pm.brd"].map((target) => {
|
||||
const source = "pm.prd";
|
||||
return {
|
||||
id: `${source}-${target}`,
|
||||
source,
|
||||
target: target,
|
||||
};
|
||||
}),
|
||||
...["pm.brd", "db.schemas", "db.postgres"].map((target) => {
|
||||
const source = "pm.drd";
|
||||
return {
|
||||
id: `${source}-${target}`,
|
||||
source,
|
||||
target: target,
|
||||
};
|
||||
}),
|
||||
...[
|
||||
"backend.specifications.openapi",
|
||||
"backend.specifications.asyncapi",
|
||||
"pm.uxdmd",
|
||||
].map((target) => {
|
||||
const source = "pm.brd";
|
||||
return {
|
||||
id: `${source}-${target}`,
|
||||
source,
|
||||
target: target,
|
||||
};
|
||||
}),
|
||||
...[
|
||||
"pm.brd",
|
||||
"backend.specifications.openapi",
|
||||
"backend.specifications.asyncapi",
|
||||
].map((source) => {
|
||||
const target = "backend.server.main";
|
||||
return {
|
||||
id: `${source}-${target}`,
|
||||
source,
|
||||
target,
|
||||
};
|
||||
}),
|
||||
|
||||
...["pm.uxdmd", "uxsitemap.structure"].map((target) => {
|
||||
const source = "pm.uxsmd";
|
||||
return {
|
||||
id: `${source}-${target}`,
|
||||
source,
|
||||
target: target,
|
||||
};
|
||||
}),
|
||||
...["uxdatamap.structure", "uxdatamap.views"].map((target) => {
|
||||
const source = "pm.uxdmd";
|
||||
return {
|
||||
id: `${source}-${target}`,
|
||||
source,
|
||||
target: target,
|
||||
};
|
||||
}),
|
||||
|
||||
...["webapp.react.root.app"].map((target) => {
|
||||
const source = "uxsitemap.structure";
|
||||
return {
|
||||
id: `${source}-${target}`,
|
||||
source,
|
||||
target: target,
|
||||
};
|
||||
}),
|
||||
...["webapp.react.root.app"].map((target) => {
|
||||
const source = "uxdatamap.views";
|
||||
return {
|
||||
id: `${source}-${target}`,
|
||||
source,
|
||||
target: target,
|
||||
};
|
||||
}),
|
||||
...["webapp.react.store.redux"].map((target) => {
|
||||
const source = "uxdatamap.structure";
|
||||
return {
|
||||
id: `${source}-${target}`,
|
||||
source,
|
||||
target: target,
|
||||
};
|
||||
}),
|
||||
|
||||
...["backend.server.main", "uxsitemap.structure"].map((target) => {
|
||||
const source = "settings.config.package";
|
||||
return {
|
||||
id: `${source}-${target}`,
|
||||
source,
|
||||
target: target,
|
||||
};
|
||||
}),
|
||||
...["uxsitemap.structure"].map((target) => {
|
||||
const source = "settings.preferences.versions";
|
||||
return {
|
||||
id: `${source}-${target}`,
|
||||
source,
|
||||
target: target,
|
||||
};
|
||||
}),
|
||||
].map((item) => {
|
||||
return {
|
||||
animated: true,
|
||||
style: { stroke: "#999" },
|
||||
type: "floating",
|
||||
markerEnd: {
|
||||
type: MarkerType.ArrowClosed,
|
||||
width: 30,
|
||||
height: 30,
|
||||
},
|
||||
...item,
|
||||
};
|
||||
}),
|
||||
};
|
||||
791
cofounder/dashboard/src/components/styles/flow.css
Normal file
791
cofounder/dashboard/src/components/styles/flow.css
Normal file
@@ -0,0 +1,791 @@
|
||||
/* this gets exported as style.css and can be used for the default theming */
|
||||
/* these are the necessary styles for React/Svelte Flow, they get used by base.css and style.css */
|
||||
|
||||
.react-flow {
|
||||
direction: ltr;
|
||||
|
||||
--xy-edge-stroke-default: #b1b1b7;
|
||||
--xy-edge-stroke-width-default: 1;
|
||||
--xy-edge-stroke-selected-default: #555;
|
||||
|
||||
--xy-connectionline-stroke-default: #b1b1b7;
|
||||
--xy-connectionline-stroke-width-default: 1;
|
||||
|
||||
--xy-attribution-background-color-default: rgba(255, 255, 255, 0.5);
|
||||
|
||||
--xy-minimap-background-color-default: #fff;
|
||||
--xy-minimap-mask-background-color-default: rgb(240, 240, 240, 0.6);
|
||||
--xy-minimap-mask-stroke-color-default: transparent;
|
||||
--xy-minimap-mask-stroke-width-default: 1;
|
||||
--xy-minimap-node-background-color-default: #e2e2e2;
|
||||
--xy-minimap-node-stroke-color-default: transparent;
|
||||
--xy-minimap-node-stroke-width-default: 2;
|
||||
|
||||
--xy-background-color-default: transparent;
|
||||
--xy-background-pattern-dots-color-default: #91919a;
|
||||
--xy-background-pattern-lines-color-default: #eee;
|
||||
--xy-background-pattern-cross-color-default: #e2e2e2;
|
||||
background-color: var(
|
||||
--xy-background-color,
|
||||
var(--xy-background-color-default)
|
||||
);
|
||||
--xy-node-color-default: inherit;
|
||||
--xy-node-border-default: 1px solid #1a192b;
|
||||
--xy-node-background-color-default: #fff;
|
||||
--xy-node-group-background-color-default: rgba(240, 240, 240, 0.25);
|
||||
--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(0, 0, 0, 0.08);
|
||||
--xy-node-boxshadow-selected-default: 0 0 0 0.5px #1a192b;
|
||||
--xy-node-border-radius-default: 3px;
|
||||
|
||||
--xy-handle-background-color-default: #1a192b;
|
||||
--xy-handle-border-color-default: #fff;
|
||||
|
||||
--xy-selection-background-color-default: rgba(0, 89, 220, 0.08);
|
||||
--xy-selection-border-default: 1px dotted rgba(0, 89, 220, 0.8);
|
||||
|
||||
--xy-controls-button-background-color-default: #fefefe;
|
||||
--xy-controls-button-background-color-hover-default: #f4f4f4;
|
||||
--xy-controls-button-color-default: inherit;
|
||||
--xy-controls-button-color-hover-default: inherit;
|
||||
--xy-controls-button-border-color-default: #eee;
|
||||
--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, 0.08);
|
||||
|
||||
--xy-edge-label-background-color-default: #ffffff;
|
||||
--xy-edge-label-color-default: inherit;
|
||||
--xy-resize-background-color-default: #3367d9;
|
||||
}
|
||||
|
||||
.react-flow.dark {
|
||||
--xy-edge-stroke-default: #5c5c5c;
|
||||
--xy-edge-stroke-width-default: 1;
|
||||
--xy-edge-stroke-selected-default: #8a8a8a;
|
||||
|
||||
--xy-connectionline-stroke-default: #4a4a4a;
|
||||
--xy-connectionline-stroke-width-default: 1;
|
||||
|
||||
--xy-attribution-background-color-default: rgba(100, 100, 100, 0.25);
|
||||
|
||||
--xy-minimap-background-color-default: #1a1a1a;
|
||||
--xy-minimap-mask-background-color-default: rgba(80, 80, 80, 0.6);
|
||||
--xy-minimap-mask-stroke-color-default: transparent;
|
||||
--xy-minimap-mask-stroke-width-default: 1;
|
||||
--xy-minimap-node-background-color-default: #3a3a3a;
|
||||
--xy-minimap-node-stroke-color-default: transparent;
|
||||
--xy-minimap-node-stroke-width-default: 2;
|
||||
|
||||
--xy-background-color-default: #1a1a1a;
|
||||
--xy-background-pattern-dots-color-default: #616161;
|
||||
--xy-background-pattern-lines-color-default: #141414;
|
||||
--xy-background-pattern-cross-color-default: #525252;
|
||||
--xy-node-color-default: #e0e0e0;
|
||||
--xy-node-border-default: 1px solid #4a4a4a;
|
||||
--xy-node-background-color-default: #2a2a2a;
|
||||
--xy-node-group-background-color-default: rgba(200, 200, 200, 0.25);
|
||||
--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(255, 255, 255, 0.08);
|
||||
--xy-node-boxshadow-selected-default: 0 0 0 0.5px #b0b0b0;
|
||||
|
||||
--xy-handle-background-color-default: #d0d0d0;
|
||||
--xy-handle-border-color-default: #2a2a2a;
|
||||
|
||||
--xy-selection-background-color-default: rgba(150, 150, 200, 0.08);
|
||||
--xy-selection-border-default: 1px dotted rgba(150, 150, 200, 0.8);
|
||||
|
||||
--xy-controls-button-background-color-default: #3a3a3a;
|
||||
--xy-controls-button-background-color-hover-default: #4a4a4a;
|
||||
--xy-controls-button-color-default: #e0e0e0;
|
||||
--xy-controls-button-color-hover-default: #ffffff;
|
||||
--xy-controls-button-border-color-default: #6a6a6a;
|
||||
--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, 0.08);
|
||||
|
||||
--xy-edge-label-background-color-default: #1a1a1a;
|
||||
--xy-edge-label-color-default: #e0e0e0;
|
||||
}
|
||||
|
||||
.react-flow__background {
|
||||
background-color: var(
|
||||
--xy-background-color,
|
||||
var(--xy-background-color-props, var(--xy-background-color-default))
|
||||
);
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.react-flow__container {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.react-flow__pane {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.react-flow__pane.draggable {
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.react-flow__pane.dragging {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.react-flow__pane.selection {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.react-flow__viewport {
|
||||
transform-origin: 0 0;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.react-flow__renderer {
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.react-flow__selection {
|
||||
z-index: 6;
|
||||
}
|
||||
|
||||
.react-flow__nodesselection-rect:focus,
|
||||
.react-flow__nodesselection-rect:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.react-flow__edge-path {
|
||||
stroke: var(--xy-edge-stroke, var(--xy-edge-stroke-default));
|
||||
stroke-width: var(--xy-edge-stroke-width, var(--xy-edge-stroke-width-default));
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.react-flow__connection-path {
|
||||
stroke: var(
|
||||
--xy-connectionline-stroke,
|
||||
var(--xy-connectionline-stroke-default)
|
||||
);
|
||||
stroke-width: var(
|
||||
--xy-connectionline-stroke-width,
|
||||
var(--xy-connectionline-stroke-width-default)
|
||||
);
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.react-flow .react-flow__edges {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.react-flow .react-flow__edges svg {
|
||||
overflow: visible;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.react-flow__edge {
|
||||
pointer-events: visibleStroke;
|
||||
}
|
||||
|
||||
.react-flow__edge.selectable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.react-flow__edge.animated path {
|
||||
stroke-dasharray: 5;
|
||||
animation: dashdraw 0.5s linear infinite;
|
||||
}
|
||||
|
||||
.react-flow__edge.animated path.react-flow__edge-interaction {
|
||||
stroke-dasharray: none;
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.react-flow__edge.inactive {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.react-flow__edge.selected,
|
||||
.react-flow__edge:focus,
|
||||
.react-flow__edge:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.react-flow__edge.selected .react-flow__edge-path,
|
||||
.react-flow__edge.selectable:focus .react-flow__edge-path,
|
||||
.react-flow__edge.selectable:focus-visible .react-flow__edge-path {
|
||||
stroke: var(--xy-edge-stroke-selected, var(--xy-edge-stroke-selected-default));
|
||||
}
|
||||
|
||||
.react-flow__edge-textwrapper {
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.react-flow__edge .react-flow__edge-text {
|
||||
pointer-events: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.react-flow__connection {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.react-flow__connection .animated {
|
||||
stroke-dasharray: 5;
|
||||
animation: dashdraw 0.5s linear infinite;
|
||||
}
|
||||
|
||||
svg.react-flow__connectionline {
|
||||
z-index: 1001;
|
||||
overflow: visible;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.react-flow__nodes {
|
||||
pointer-events: none;
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
|
||||
.react-flow__node {
|
||||
position: absolute;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
pointer-events: all;
|
||||
transform-origin: 0 0;
|
||||
box-sizing: border-box;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.react-flow__node.selectable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.react-flow__node.draggable {
|
||||
cursor: grab;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.react-flow__node.draggable.dragging {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.react-flow__nodesselection {
|
||||
z-index: 3;
|
||||
transform-origin: left top;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.react-flow__nodesselection-rect {
|
||||
position: absolute;
|
||||
pointer-events: all;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.react-flow__handle {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
min-width: 5px;
|
||||
min-height: 5px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background-color: var(
|
||||
--xy-handle-background-color,
|
||||
var(--xy-handle-background-color-default)
|
||||
);
|
||||
border: 1px solid
|
||||
var(--xy-handle-border-color, var(--xy-handle-border-color-default));
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
.react-flow__handle.connectingfrom {
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.react-flow__handle.connectionindicator {
|
||||
pointer-events: all;
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
.react-flow__handle-bottom {
|
||||
top: auto;
|
||||
left: 50%;
|
||||
bottom: 0;
|
||||
transform: translate(-50%, 50%);
|
||||
}
|
||||
|
||||
.react-flow__handle-top {
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.react-flow__handle-left {
|
||||
top: 50%;
|
||||
left: 0;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.react-flow__handle-right {
|
||||
top: 50%;
|
||||
right: 0;
|
||||
transform: translate(50%, -50%);
|
||||
}
|
||||
|
||||
.react-flow__edgeupdater {
|
||||
cursor: move;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.react-flow__panel {
|
||||
position: absolute;
|
||||
z-index: 5;
|
||||
margin: 15px;
|
||||
}
|
||||
|
||||
.react-flow__panel.top {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.react-flow__panel.bottom {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.react-flow__panel.left {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.react-flow__panel.right {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.react-flow__panel.center {
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.react-flow__attribution {
|
||||
font-size: 10px;
|
||||
background: var(
|
||||
--xy-attribution-background-color,
|
||||
var(--xy-attribution-background-color-default)
|
||||
);
|
||||
padding: 2px 3px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.react-flow__attribution a {
|
||||
text-decoration: none;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
@keyframes dashdraw {
|
||||
from {
|
||||
stroke-dashoffset: 10;
|
||||
}
|
||||
}
|
||||
|
||||
.react-flow__edgelabel-renderer {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.react-flow__viewport-portal {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.react-flow__minimap {
|
||||
background: var(
|
||||
--xy-minimap-background-color-props,
|
||||
var(--xy-minimap-background-color, var(--xy-minimap-background-color-default))
|
||||
);
|
||||
}
|
||||
|
||||
.react-flow__minimap-svg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.react-flow__minimap-mask {
|
||||
fill: var(
|
||||
--xy-minimap-mask-background-color-props,
|
||||
var(
|
||||
--xy-minimap-mask-background-color,
|
||||
var(--xy-minimap-mask-background-color-default)
|
||||
)
|
||||
);
|
||||
stroke: var(
|
||||
--xy-minimap-mask-stroke-color-props,
|
||||
var(
|
||||
--xy-minimap-mask-stroke-color,
|
||||
var(--xy-minimap-mask-stroke-color-default)
|
||||
)
|
||||
);
|
||||
stroke-width: var(
|
||||
--xy-minimap-mask-stroke-width-props,
|
||||
var(
|
||||
--xy-minimap-mask-stroke-width,
|
||||
var(--xy-minimap-mask-stroke-width-default)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
.react-flow__minimap-node {
|
||||
fill: var(
|
||||
--xy-minimap-node-background-color-props,
|
||||
var(
|
||||
--xy-minimap-node-background-color,
|
||||
var(--xy-minimap-node-background-color-default)
|
||||
)
|
||||
);
|
||||
stroke: var(
|
||||
--xy-minimap-node-stroke-color-props,
|
||||
var(
|
||||
--xy-minimap-node-stroke-color,
|
||||
var(--xy-minimap-node-stroke-color-default)
|
||||
)
|
||||
);
|
||||
stroke-width: var(
|
||||
--xy-minimap-node-stroke-width-props,
|
||||
var(
|
||||
--xy-minimap-node-stroke-width,
|
||||
var(--xy-minimap-node-stroke-width-default)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
.react-flow__background-pattern.dots {
|
||||
fill: var(
|
||||
--xy-background-pattern-color-props,
|
||||
var(
|
||||
--xy-background-pattern-color,
|
||||
var(--xy-background-pattern-dots-color-default)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
.react-flow__background-pattern.lines {
|
||||
stroke: var(
|
||||
--xy-background-pattern-color-props,
|
||||
var(
|
||||
--xy-background-pattern-color,
|
||||
var(--xy-background-pattern-lines-color-default)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
.react-flow__background-pattern.cross {
|
||||
stroke: var(
|
||||
--xy-background-pattern-color-props,
|
||||
var(
|
||||
--xy-background-pattern-color,
|
||||
var(--xy-background-pattern-cross-color-default)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
.react-flow__controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: var(
|
||||
--xy-controls-box-shadow,
|
||||
var(--xy-controls-box-shadow-default)
|
||||
);
|
||||
}
|
||||
|
||||
.react-flow__controls.horizontal {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.react-flow__controls-button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
padding: 4px;
|
||||
border: none;
|
||||
background: var(
|
||||
--xy-controls-button-background-color,
|
||||
var(--xy-controls-button-background-color-default)
|
||||
);
|
||||
border-bottom: 1px solid
|
||||
var(
|
||||
--xy-controls-button-border-color-props,
|
||||
var(
|
||||
--xy-controls-button-border-color,
|
||||
var(--xy-controls-button-border-color-default)
|
||||
)
|
||||
);
|
||||
color: var(
|
||||
--xy-controls-button-color-props,
|
||||
var(--xy-controls-button-color, var(--xy-controls-button-color-default))
|
||||
);
|
||||
cursor: pointer;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.react-flow__controls-button svg {
|
||||
width: 100%;
|
||||
max-width: 12px;
|
||||
max-height: 12px;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.react-flow__edge.updating .react-flow__edge-path {
|
||||
stroke: #777;
|
||||
}
|
||||
|
||||
.react-flow__edge-text {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.react-flow__node.selectable:focus,
|
||||
.react-flow__node.selectable:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.react-flow__node-input,
|
||||
.react-flow__node-default,
|
||||
.react-flow__node-output,
|
||||
.react-flow__node-group {
|
||||
padding: 10px;
|
||||
border-radius: var(
|
||||
--xy-node-border-radius,
|
||||
var(--xy-node-border-radius-default)
|
||||
);
|
||||
width: 150px;
|
||||
font-size: 12px;
|
||||
color: var(--xy-node-color, var(--xy-node-color-default));
|
||||
text-align: center;
|
||||
border: var(--xy-node-border, var(--xy-node-border-default));
|
||||
background-color: var(
|
||||
--xy-node-background-color,
|
||||
var(--xy-node-background-color-default)
|
||||
);
|
||||
}
|
||||
|
||||
.react-flow__node-input.selectable:hover,
|
||||
.react-flow__node-default.selectable:hover,
|
||||
.react-flow__node-output.selectable:hover,
|
||||
.react-flow__node-group.selectable:hover {
|
||||
box-shadow: var(
|
||||
--xy-node-boxshadow-hover,
|
||||
var(--xy-node-boxshadow-hover-default)
|
||||
);
|
||||
}
|
||||
|
||||
.react-flow__node-input.selectable.selected,
|
||||
.react-flow__node-input.selectable:focus,
|
||||
.react-flow__node-input.selectable:focus-visible,
|
||||
.react-flow__node-default.selectable.selected,
|
||||
.react-flow__node-default.selectable:focus,
|
||||
.react-flow__node-default.selectable:focus-visible,
|
||||
.react-flow__node-output.selectable.selected,
|
||||
.react-flow__node-output.selectable:focus,
|
||||
.react-flow__node-output.selectable:focus-visible,
|
||||
.react-flow__node-group.selectable.selected,
|
||||
.react-flow__node-group.selectable:focus,
|
||||
.react-flow__node-group.selectable:focus-visible {
|
||||
box-shadow: var(
|
||||
--xy-node-boxshadow-selected,
|
||||
var(--xy-node-boxshadow-selected-default)
|
||||
);
|
||||
}
|
||||
|
||||
.react-flow__node-group {
|
||||
background-color: var(
|
||||
--xy-node-group-background-color,
|
||||
var(--xy-node-group-background-color-default)
|
||||
);
|
||||
}
|
||||
|
||||
.react-flow__nodesselection-rect,
|
||||
.react-flow__selection {
|
||||
background: var(
|
||||
--xy-selection-background-color,
|
||||
var(--xy-selection-background-color-default)
|
||||
);
|
||||
border: var(--xy-selection-border, var(--xy-selection-border-default));
|
||||
}
|
||||
|
||||
.react-flow__nodesselection-rect:focus,
|
||||
.react-flow__nodesselection-rect:focus-visible,
|
||||
.react-flow__selection:focus,
|
||||
.react-flow__selection:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.react-flow__controls-button:hover {
|
||||
background: var(
|
||||
--xy-controls-button-background-color-hover-props,
|
||||
var(
|
||||
--xy-controls-button-background-color-hover,
|
||||
var(--xy-controls-button-background-color-hover-default)
|
||||
)
|
||||
);
|
||||
color: var(
|
||||
--xy-controls-button-color-hover-props,
|
||||
var(
|
||||
--xy-controls-button-color-hover,
|
||||
var(--xy-controls-button-color-hover-default)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
.react-flow__controls-button:disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.react-flow__controls-button:disabled svg {
|
||||
fill-opacity: 0.4;
|
||||
}
|
||||
|
||||
.react-flow__controls-button:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.react-flow__resize-control {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.react-flow__resize-control.left,
|
||||
.react-flow__resize-control.right {
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
.react-flow__resize-control.top,
|
||||
.react-flow__resize-control.bottom {
|
||||
cursor: ns-resize;
|
||||
}
|
||||
|
||||
.react-flow__resize-control.top.left,
|
||||
.react-flow__resize-control.bottom.right {
|
||||
cursor: nwse-resize;
|
||||
}
|
||||
|
||||
.react-flow__resize-control.bottom.left,
|
||||
.react-flow__resize-control.top.right {
|
||||
cursor: nesw-resize;
|
||||
}
|
||||
|
||||
/* handle styles */
|
||||
.react-flow__resize-control.handle {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
border: 1px solid #fff;
|
||||
border-radius: 1px;
|
||||
background-color: var(
|
||||
--xy-resize-background-color,
|
||||
var(--xy-resize-background-color-default)
|
||||
);
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.react-flow__resize-control.handle.left {
|
||||
left: 0;
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
.react-flow__resize-control.handle.right {
|
||||
left: 100%;
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
.react-flow__resize-control.handle.top {
|
||||
left: 50%;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.react-flow__resize-control.handle.bottom {
|
||||
left: 50%;
|
||||
top: 100%;
|
||||
}
|
||||
|
||||
.react-flow__resize-control.handle.top.left {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.react-flow__resize-control.handle.bottom.left {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.react-flow__resize-control.handle.top.right {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
.react-flow__resize-control.handle.bottom.right {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
/* line styles */
|
||||
.react-flow__resize-control.line {
|
||||
border-color: var(
|
||||
--xy-resize-background-color,
|
||||
var(--xy-resize-background-color-default)
|
||||
);
|
||||
border-width: 0;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.react-flow__resize-control.line.left,
|
||||
.react-flow__resize-control.line.right {
|
||||
width: 1px;
|
||||
transform: translate(-50%, 0);
|
||||
top: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.react-flow__resize-control.line.left {
|
||||
left: 0;
|
||||
border-left-width: 1px;
|
||||
}
|
||||
|
||||
.react-flow__resize-control.line.right {
|
||||
left: 100%;
|
||||
border-right-width: 1px;
|
||||
}
|
||||
|
||||
.react-flow__resize-control.line.top,
|
||||
.react-flow__resize-control.line.bottom {
|
||||
height: 1px;
|
||||
transform: translate(0, -50%);
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.react-flow__resize-control.line.top {
|
||||
top: 0;
|
||||
border-top-width: 1px;
|
||||
}
|
||||
|
||||
.react-flow__resize-control.line.bottom {
|
||||
border-bottom-width: 1px;
|
||||
top: 100%;
|
||||
}
|
||||
|
||||
.react-flow__edge-textbg {
|
||||
fill: var(
|
||||
--xy-edge-label-background-color,
|
||||
var(--xy-edge-label-background-color-default)
|
||||
);
|
||||
}
|
||||
|
||||
.react-flow__edge-text {
|
||||
fill: var(--xy-edge-label-color, var(--xy-edge-label-color-default));
|
||||
}
|
||||
56
cofounder/dashboard/src/components/ui/accordion.tsx
Normal file
56
cofounder/dashboard/src/components/ui/accordion.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import * as React from "react";
|
||||
import * as AccordionPrimitive from "@radix-ui/react-accordion";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Accordion = AccordionPrimitive.Root;
|
||||
|
||||
const AccordionItem = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AccordionPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn("border-b", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
AccordionItem.displayName = "AccordionItem";
|
||||
|
||||
const AccordionTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<AccordionPrimitive.Header className="flex">
|
||||
<AccordionPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
||||
));
|
||||
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
|
||||
|
||||
const AccordionContent = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<AccordionPrimitive.Content
|
||||
ref={ref}
|
||||
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
||||
{...props}
|
||||
>
|
||||
<div className={cn("pb-4 pt-0", className)}>{children}</div>
|
||||
</AccordionPrimitive.Content>
|
||||
));
|
||||
|
||||
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
|
||||
|
||||
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
|
||||
136
cofounder/dashboard/src/components/ui/alert-dialog.tsx
Normal file
136
cofounder/dashboard/src/components/ui/alert-dialog.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import * as React from "react";
|
||||
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
|
||||
const AlertDialog = AlertDialogPrimitive.Root;
|
||||
|
||||
const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
|
||||
|
||||
const AlertDialogPortal = AlertDialogPrimitive.Portal;
|
||||
|
||||
const AlertDialogOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Overlay
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
));
|
||||
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
|
||||
|
||||
const AlertDialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPortal>
|
||||
<AlertDialogOverlay />
|
||||
<AlertDialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-neutral-200 bg-white p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg dark:border-neutral-800 dark:bg-neutral-950",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</AlertDialogPortal>
|
||||
));
|
||||
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
|
||||
|
||||
const AlertDialogHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn("flex flex-col space-y-2 text-center sm:text-left", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
AlertDialogHeader.displayName = "AlertDialogHeader";
|
||||
|
||||
const AlertDialogFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
AlertDialogFooter.displayName = "AlertDialogFooter";
|
||||
|
||||
const AlertDialogTitle = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn("text-lg font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
|
||||
|
||||
const AlertDialogDescription = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-neutral-500 dark:text-neutral-400", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
AlertDialogDescription.displayName =
|
||||
AlertDialogPrimitive.Description.displayName;
|
||||
|
||||
const AlertDialogAction = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Action>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Action
|
||||
ref={ref}
|
||||
className={cn(buttonVariants(), className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
|
||||
|
||||
const AlertDialogCancel = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Cancel
|
||||
ref={ref}
|
||||
className={cn(
|
||||
buttonVariants({ variant: "outline" }),
|
||||
"mt-2 sm:mt-0",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
|
||||
|
||||
export {
|
||||
AlertDialog,
|
||||
AlertDialogPortal,
|
||||
AlertDialogOverlay,
|
||||
AlertDialogTrigger,
|
||||
AlertDialogContent,
|
||||
AlertDialogHeader,
|
||||
AlertDialogFooter,
|
||||
AlertDialogTitle,
|
||||
AlertDialogDescription,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
};
|
||||
60
cofounder/dashboard/src/components/ui/alert.tsx
Normal file
60
cofounder/dashboard/src/components/ui/alert.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import * as React from "react";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const alertVariants = cva(
|
||||
"relative w-full rounded-lg border border-neutral-200 p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-neutral-950 dark:border-neutral-800 dark:[&>svg]:text-neutral-50",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"bg-white text-neutral-950 dark:bg-neutral-950 dark:text-neutral-50",
|
||||
destructive:
|
||||
"border-red-500/50 text-red-500 dark:border-red-500 [&>svg]:text-red-500 dark:border-red-900/50 dark:text-red-900 dark:dark:border-red-900 dark:[&>svg]:text-red-900",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const Alert = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
|
||||
>(({ className, variant, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
role="alert"
|
||||
className={cn(alertVariants({ variant }), className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Alert.displayName = "Alert";
|
||||
|
||||
const AlertTitle = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLHeadingElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<h5
|
||||
ref={ref}
|
||||
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
AlertTitle.displayName = "AlertTitle";
|
||||
|
||||
const AlertDescription = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("text-sm [&_p]:leading-relaxed", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
AlertDescription.displayName = "AlertDescription";
|
||||
|
||||
export { Alert, AlertTitle, AlertDescription };
|
||||
5
cofounder/dashboard/src/components/ui/aspect-ratio.tsx
Normal file
5
cofounder/dashboard/src/components/ui/aspect-ratio.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio";
|
||||
|
||||
const AspectRatio = AspectRatioPrimitive.Root;
|
||||
|
||||
export { AspectRatio };
|
||||
48
cofounder/dashboard/src/components/ui/avatar.tsx
Normal file
48
cofounder/dashboard/src/components/ui/avatar.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import * as React from "react";
|
||||
import * as AvatarPrimitive from "@radix-ui/react-avatar";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Avatar = React.forwardRef<
|
||||
React.ElementRef<typeof AvatarPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AvatarPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Avatar.displayName = AvatarPrimitive.Root.displayName;
|
||||
|
||||
const AvatarImage = React.forwardRef<
|
||||
React.ElementRef<typeof AvatarPrimitive.Image>,
|
||||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AvatarPrimitive.Image
|
||||
ref={ref}
|
||||
className={cn("aspect-square h-full w-full", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
AvatarImage.displayName = AvatarPrimitive.Image.displayName;
|
||||
|
||||
const AvatarFallback = React.forwardRef<
|
||||
React.ElementRef<typeof AvatarPrimitive.Fallback>,
|
||||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AvatarPrimitive.Fallback
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-full w-full items-center justify-center rounded-full bg-neutral-100 dark:bg-neutral-800",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
|
||||
|
||||
export { Avatar, AvatarImage, AvatarFallback };
|
||||
36
cofounder/dashboard/src/components/ui/badge.tsx
Normal file
36
cofounder/dashboard/src/components/ui/badge.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import * as React from "react";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const badgeVariants = cva(
|
||||
"inline-flex items-center rounded-full border border-neutral-200 px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-neutral-950 focus:ring-offset-2 dark:border-neutral-800 dark:focus:ring-neutral-300",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-transparent bg-neutral-900 text-neutral-50 hover:bg-neutral-900/80 dark:bg-neutral-50 dark:text-neutral-900 dark:hover:bg-neutral-50/80",
|
||||
secondary:
|
||||
"border-transparent bg-neutral-100 text-neutral-900 hover:bg-neutral-100/80 dark:bg-neutral-800 dark:text-neutral-50 dark:hover:bg-neutral-800/80",
|
||||
destructive:
|
||||
"border-transparent bg-red-500 text-neutral-50 hover:bg-red-500/80 dark:bg-red-900 dark:text-neutral-50 dark:hover:bg-red-900/80",
|
||||
outline: "text-neutral-950 dark:text-neutral-50",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export interface BadgeProps
|
||||
extends React.HTMLAttributes<HTMLDivElement>,
|
||||
VariantProps<typeof badgeVariants> {}
|
||||
|
||||
function Badge({ className, variant, ...props }: BadgeProps) {
|
||||
return (
|
||||
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants };
|
||||
118
cofounder/dashboard/src/components/ui/breadcrumb.tsx
Normal file
118
cofounder/dashboard/src/components/ui/breadcrumb.tsx
Normal file
@@ -0,0 +1,118 @@
|
||||
import * as React from "react";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { ChevronRight, MoreHorizontal } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Breadcrumb = React.forwardRef<
|
||||
HTMLElement,
|
||||
React.ComponentPropsWithoutRef<"nav"> & {
|
||||
separator?: React.ReactNode;
|
||||
}
|
||||
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />);
|
||||
Breadcrumb.displayName = "Breadcrumb";
|
||||
|
||||
const BreadcrumbList = React.forwardRef<
|
||||
HTMLOListElement,
|
||||
React.ComponentPropsWithoutRef<"ol">
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ol
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex flex-wrap items-center gap-1.5 break-words text-sm text-neutral-500 sm:gap-2.5 dark:text-neutral-400",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
BreadcrumbList.displayName = "BreadcrumbList";
|
||||
|
||||
const BreadcrumbItem = React.forwardRef<
|
||||
HTMLLIElement,
|
||||
React.ComponentPropsWithoutRef<"li">
|
||||
>(({ className, ...props }, ref) => (
|
||||
<li
|
||||
ref={ref}
|
||||
className={cn("inline-flex items-center gap-1.5", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
BreadcrumbItem.displayName = "BreadcrumbItem";
|
||||
|
||||
const BreadcrumbLink = React.forwardRef<
|
||||
HTMLAnchorElement,
|
||||
React.ComponentPropsWithoutRef<"a"> & {
|
||||
asChild?: boolean;
|
||||
}
|
||||
>(({ asChild, className, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "a";
|
||||
|
||||
return (
|
||||
<Comp
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"transition-colors hover:text-neutral-950 dark:hover:text-neutral-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
BreadcrumbLink.displayName = "BreadcrumbLink";
|
||||
|
||||
const BreadcrumbPage = React.forwardRef<
|
||||
HTMLSpanElement,
|
||||
React.ComponentPropsWithoutRef<"span">
|
||||
>(({ className, ...props }, ref) => (
|
||||
<span
|
||||
ref={ref}
|
||||
role="link"
|
||||
aria-disabled="true"
|
||||
aria-current="page"
|
||||
className={cn("font-normal text-neutral-950 dark:text-neutral-50", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
BreadcrumbPage.displayName = "BreadcrumbPage";
|
||||
|
||||
const BreadcrumbSeparator = ({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"li">) => (
|
||||
<li
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
className={cn("[&>svg]:size-3.5", className)}
|
||||
{...props}
|
||||
>
|
||||
{children ?? <ChevronRight />}
|
||||
</li>
|
||||
);
|
||||
BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
|
||||
|
||||
const BreadcrumbEllipsis = ({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"span">) => (
|
||||
<span
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
className={cn("flex h-9 w-9 items-center justify-center", className)}
|
||||
{...props}
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
<span className="sr-only">More</span>
|
||||
</span>
|
||||
);
|
||||
BreadcrumbEllipsis.displayName = "BreadcrumbElipssis";
|
||||
|
||||
export {
|
||||
Breadcrumb,
|
||||
BreadcrumbList,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
BreadcrumbEllipsis,
|
||||
};
|
||||
59
cofounder/dashboard/src/components/ui/button.tsx
Normal file
59
cofounder/dashboard/src/components/ui/button.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import * as React from "react";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 dark:ring-offset-neutral-950 dark:focus-visible:ring-neutral-300",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"bg-neutral-900 text-neutral-50 hover:bg-neutral-900/90 dark:bg-neutral-50 dark:text-neutral-900 dark:hover:bg-neutral-50/90",
|
||||
destructive:
|
||||
"bg-red-500 text-neutral-50 hover:bg-red-500/90 dark:bg-red-900 dark:text-neutral-50 dark:hover:bg-red-900/90",
|
||||
outline:
|
||||
"border border-neutral-200 bg-white hover:bg-neutral-100 hover:text-neutral-900 dark:border-neutral-800 dark:bg-neutral-950 dark:hover:bg-neutral-800 dark:hover:text-neutral-50",
|
||||
secondary:
|
||||
"bg-neutral-100 text-neutral-900 hover:bg-neutral-100/80 dark:bg-neutral-800 dark:text-neutral-50 dark:hover:bg-neutral-800/80",
|
||||
ghost:
|
||||
"hover:bg-neutral-100 hover:text-neutral-900 dark:hover:bg-neutral-800 dark:hover:text-neutral-50",
|
||||
link:
|
||||
"text-neutral-900 underline-offset-4 hover:underline dark:text-neutral-50",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-4 py-2",
|
||||
sm: "h-9 rounded-md px-3",
|
||||
lg: "h-11 rounded-md px-8",
|
||||
icon: "h-10 w-10",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean;
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button";
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
Button.displayName = "Button";
|
||||
|
||||
export { Button, buttonVariants };
|
||||
66
cofounder/dashboard/src/components/ui/calendar.tsx
Normal file
66
cofounder/dashboard/src/components/ui/calendar.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import * as React from "react";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { DayPicker } from "react-day-picker";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
|
||||
export type CalendarProps = React.ComponentProps<typeof DayPicker>;
|
||||
|
||||
function Calendar({
|
||||
className,
|
||||
classNames,
|
||||
showOutsideDays = true,
|
||||
...props
|
||||
}: CalendarProps) {
|
||||
return (
|
||||
<DayPicker
|
||||
showOutsideDays={showOutsideDays}
|
||||
className={cn("p-3", className)}
|
||||
classNames={{
|
||||
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
|
||||
month: "space-y-4",
|
||||
caption: "flex justify-center pt-1 relative items-center",
|
||||
caption_label: "text-sm font-medium",
|
||||
nav: "space-x-1 flex items-center",
|
||||
nav_button: cn(
|
||||
buttonVariants({ variant: "outline" }),
|
||||
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100",
|
||||
),
|
||||
nav_button_previous: "absolute left-1",
|
||||
nav_button_next: "absolute right-1",
|
||||
table: "w-full border-collapse space-y-1",
|
||||
head_row: "flex",
|
||||
head_cell:
|
||||
"text-neutral-500 rounded-md w-9 font-normal text-[0.8rem] dark:text-neutral-400",
|
||||
row: "flex w-full mt-2",
|
||||
cell:
|
||||
"h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-neutral-100/50 [&:has([aria-selected])]:bg-neutral-100 first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20 dark:[&:has([aria-selected].day-outside)]:bg-neutral-800/50 dark:[&:has([aria-selected])]:bg-neutral-800",
|
||||
day: cn(
|
||||
buttonVariants({ variant: "ghost" }),
|
||||
"h-9 w-9 p-0 font-normal aria-selected:opacity-100",
|
||||
),
|
||||
day_range_end: "day-range-end",
|
||||
day_selected:
|
||||
"bg-neutral-900 text-neutral-50 hover:bg-neutral-900 hover:text-neutral-50 focus:bg-neutral-900 focus:text-neutral-50 dark:bg-neutral-50 dark:text-neutral-900 dark:hover:bg-neutral-50 dark:hover:text-neutral-900 dark:focus:bg-neutral-50 dark:focus:text-neutral-900",
|
||||
day_today:
|
||||
"bg-neutral-100 text-neutral-900 dark:bg-neutral-800 dark:text-neutral-50",
|
||||
day_outside:
|
||||
"day-outside text-neutral-500 opacity-50 aria-selected:bg-neutral-100/50 aria-selected:text-neutral-500 aria-selected:opacity-30 dark:text-neutral-400 dark:aria-selected:bg-neutral-800/50 dark:aria-selected:text-neutral-400",
|
||||
day_disabled: "text-neutral-500 opacity-50 dark:text-neutral-400",
|
||||
day_range_middle:
|
||||
"aria-selected:bg-neutral-100 aria-selected:text-neutral-900 dark:aria-selected:bg-neutral-800 dark:aria-selected:text-neutral-50",
|
||||
day_hidden: "invisible",
|
||||
...classNames,
|
||||
}}
|
||||
components={{
|
||||
IconLeft: ({ ...props }) => <ChevronLeft className="h-4 w-4" />,
|
||||
IconRight: ({ ...props }) => <ChevronRight className="h-4 w-4" />,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Calendar.displayName = "Calendar";
|
||||
|
||||
export { Calendar };
|
||||
86
cofounder/dashboard/src/components/ui/card.tsx
Normal file
86
cofounder/dashboard/src/components/ui/card.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Card = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"rounded-lg border border-neutral-200 bg-white text-neutral-950 shadow-sm dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Card.displayName = "Card";
|
||||
|
||||
const CardHeader = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
CardHeader.displayName = "CardHeader";
|
||||
|
||||
const CardTitle = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLHeadingElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<h3
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"text-2xl font-semibold leading-none tracking-tight",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
CardTitle.displayName = "CardTitle";
|
||||
|
||||
const CardDescription = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<p
|
||||
ref={ref}
|
||||
className={cn("text-sm text-neutral-500 dark:text-neutral-400", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
CardDescription.displayName = "CardDescription";
|
||||
|
||||
const CardContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
||||
));
|
||||
CardContent.displayName = "CardContent";
|
||||
|
||||
const CardFooter = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("flex items-center p-6 pt-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
CardFooter.displayName = "CardFooter";
|
||||
|
||||
export {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardFooter,
|
||||
CardTitle,
|
||||
CardDescription,
|
||||
CardContent,
|
||||
};
|
||||
260
cofounder/dashboard/src/components/ui/carousel.tsx
Normal file
260
cofounder/dashboard/src/components/ui/carousel.tsx
Normal file
@@ -0,0 +1,260 @@
|
||||
import * as React from "react";
|
||||
import useEmblaCarousel, {
|
||||
type UseEmblaCarouselType,
|
||||
} from "embla-carousel-react";
|
||||
import { ArrowLeft, ArrowRight } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
type CarouselApi = UseEmblaCarouselType[1];
|
||||
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
|
||||
type CarouselOptions = UseCarouselParameters[0];
|
||||
type CarouselPlugin = UseCarouselParameters[1];
|
||||
|
||||
type CarouselProps = {
|
||||
opts?: CarouselOptions;
|
||||
plugins?: CarouselPlugin;
|
||||
orientation?: "horizontal" | "vertical";
|
||||
setApi?: (api: CarouselApi) => void;
|
||||
};
|
||||
|
||||
type CarouselContextProps = {
|
||||
carouselRef: ReturnType<typeof useEmblaCarousel>[0];
|
||||
api: ReturnType<typeof useEmblaCarousel>[1];
|
||||
scrollPrev: () => void;
|
||||
scrollNext: () => void;
|
||||
canScrollPrev: boolean;
|
||||
canScrollNext: boolean;
|
||||
} & CarouselProps;
|
||||
|
||||
const CarouselContext = React.createContext<CarouselContextProps | null>(null);
|
||||
|
||||
function useCarousel() {
|
||||
const context = React.useContext(CarouselContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error("useCarousel must be used within a <Carousel />");
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
const Carousel = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement> & CarouselProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
orientation = "horizontal",
|
||||
opts,
|
||||
setApi,
|
||||
plugins,
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const [carouselRef, api] = useEmblaCarousel(
|
||||
{
|
||||
...opts,
|
||||
axis: orientation === "horizontal" ? "x" : "y",
|
||||
},
|
||||
plugins,
|
||||
);
|
||||
const [canScrollPrev, setCanScrollPrev] = React.useState(false);
|
||||
const [canScrollNext, setCanScrollNext] = React.useState(false);
|
||||
|
||||
const onSelect = React.useCallback((api: CarouselApi) => {
|
||||
if (!api) {
|
||||
return;
|
||||
}
|
||||
|
||||
setCanScrollPrev(api.canScrollPrev());
|
||||
setCanScrollNext(api.canScrollNext());
|
||||
}, []);
|
||||
|
||||
const scrollPrev = React.useCallback(() => {
|
||||
api?.scrollPrev();
|
||||
}, [api]);
|
||||
|
||||
const scrollNext = React.useCallback(() => {
|
||||
api?.scrollNext();
|
||||
}, [api]);
|
||||
|
||||
const handleKeyDown = React.useCallback(
|
||||
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (event.key === "ArrowLeft") {
|
||||
event.preventDefault();
|
||||
scrollPrev();
|
||||
} else if (event.key === "ArrowRight") {
|
||||
event.preventDefault();
|
||||
scrollNext();
|
||||
}
|
||||
},
|
||||
[scrollPrev, scrollNext],
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!api || !setApi) {
|
||||
return;
|
||||
}
|
||||
|
||||
setApi(api);
|
||||
}, [api, setApi]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!api) {
|
||||
return;
|
||||
}
|
||||
|
||||
onSelect(api);
|
||||
api.on("reInit", onSelect);
|
||||
api.on("select", onSelect);
|
||||
|
||||
return () => {
|
||||
api?.off("select", onSelect);
|
||||
};
|
||||
}, [api, onSelect]);
|
||||
|
||||
return (
|
||||
<CarouselContext.Provider
|
||||
value={{
|
||||
carouselRef,
|
||||
api: api,
|
||||
opts,
|
||||
orientation:
|
||||
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
|
||||
scrollPrev,
|
||||
scrollNext,
|
||||
canScrollPrev,
|
||||
canScrollNext,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
ref={ref}
|
||||
onKeyDownCapture={handleKeyDown}
|
||||
className={cn("relative", className)}
|
||||
role="region"
|
||||
aria-roledescription="carousel"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</CarouselContext.Provider>
|
||||
);
|
||||
},
|
||||
);
|
||||
Carousel.displayName = "Carousel";
|
||||
|
||||
const CarouselContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { carouselRef, orientation } = useCarousel();
|
||||
|
||||
return (
|
||||
<div ref={carouselRef} className="overflow-hidden">
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex",
|
||||
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
CarouselContent.displayName = "CarouselContent";
|
||||
|
||||
const CarouselItem = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { orientation } = useCarousel();
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
role="group"
|
||||
aria-roledescription="slide"
|
||||
className={cn(
|
||||
"min-w-0 shrink-0 grow-0 basis-full",
|
||||
orientation === "horizontal" ? "pl-4" : "pt-4",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
CarouselItem.displayName = "CarouselItem";
|
||||
|
||||
const CarouselPrevious = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
React.ComponentProps<typeof Button>
|
||||
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
|
||||
const { orientation, scrollPrev, canScrollPrev } = useCarousel();
|
||||
|
||||
return (
|
||||
<Button
|
||||
ref={ref}
|
||||
variant={variant}
|
||||
size={size}
|
||||
className={cn(
|
||||
"absolute h-8 w-8 rounded-full",
|
||||
orientation === "horizontal"
|
||||
? "-left-12 top-1/2 -translate-y-1/2"
|
||||
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
|
||||
className,
|
||||
)}
|
||||
disabled={!canScrollPrev}
|
||||
onClick={scrollPrev}
|
||||
{...props}
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
<span className="sr-only">Previous slide</span>
|
||||
</Button>
|
||||
);
|
||||
});
|
||||
CarouselPrevious.displayName = "CarouselPrevious";
|
||||
|
||||
const CarouselNext = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
React.ComponentProps<typeof Button>
|
||||
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
|
||||
const { orientation, scrollNext, canScrollNext } = useCarousel();
|
||||
|
||||
return (
|
||||
<Button
|
||||
ref={ref}
|
||||
variant={variant}
|
||||
size={size}
|
||||
className={cn(
|
||||
"absolute h-8 w-8 rounded-full",
|
||||
orientation === "horizontal"
|
||||
? "-right-12 top-1/2 -translate-y-1/2"
|
||||
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
|
||||
className,
|
||||
)}
|
||||
disabled={!canScrollNext}
|
||||
onClick={scrollNext}
|
||||
{...props}
|
||||
>
|
||||
<ArrowRight className="h-4 w-4" />
|
||||
<span className="sr-only">Next slide</span>
|
||||
</Button>
|
||||
);
|
||||
});
|
||||
CarouselNext.displayName = "CarouselNext";
|
||||
|
||||
export {
|
||||
type CarouselApi,
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselPrevious,
|
||||
CarouselNext,
|
||||
};
|
||||
361
cofounder/dashboard/src/components/ui/chart.tsx
Normal file
361
cofounder/dashboard/src/components/ui/chart.tsx
Normal file
@@ -0,0 +1,361 @@
|
||||
import * as React from "react";
|
||||
import * as RechartsPrimitive from "recharts";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
// Format: { THEME_NAME: CSS_SELECTOR }
|
||||
const THEMES = { light: "", dark: ".dark" } as const;
|
||||
|
||||
export type ChartConfig = {
|
||||
[k in string]: {
|
||||
label?: React.ReactNode;
|
||||
icon?: React.ComponentType;
|
||||
} & (
|
||||
| { color?: string; theme?: never }
|
||||
| { color?: never; theme: Record<keyof typeof THEMES, string> }
|
||||
);
|
||||
};
|
||||
|
||||
type ChartContextProps = {
|
||||
config: ChartConfig;
|
||||
};
|
||||
|
||||
const ChartContext = React.createContext<ChartContextProps | null>(null);
|
||||
|
||||
function useChart() {
|
||||
const context = React.useContext(ChartContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error("useChart must be used within a <ChartContainer />");
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
const ChartContainer = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.ComponentProps<"div"> & {
|
||||
config: ChartConfig;
|
||||
children: React.ComponentProps<
|
||||
typeof RechartsPrimitive.ResponsiveContainer
|
||||
>["children"];
|
||||
}
|
||||
>(({ id, className, children, config, ...props }, ref) => {
|
||||
const uniqueId = React.useId();
|
||||
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`;
|
||||
|
||||
return (
|
||||
<ChartContext.Provider value={{ config }}>
|
||||
<div
|
||||
data-chart={chartId}
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChartStyle id={chartId} config={config} />
|
||||
<RechartsPrimitive.ResponsiveContainer>
|
||||
{children}
|
||||
</RechartsPrimitive.ResponsiveContainer>
|
||||
</div>
|
||||
</ChartContext.Provider>
|
||||
);
|
||||
});
|
||||
ChartContainer.displayName = "Chart";
|
||||
|
||||
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
|
||||
const colorConfig = Object.entries(config).filter(
|
||||
([_, config]) => config.theme || config.color,
|
||||
);
|
||||
|
||||
if (!colorConfig.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<style
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: Object.entries(THEMES)
|
||||
.map(
|
||||
([theme, prefix]) => `
|
||||
${prefix} [data-chart=${id}] {
|
||||
${colorConfig
|
||||
.map(([key, itemConfig]) => {
|
||||
const color =
|
||||
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
|
||||
itemConfig.color;
|
||||
return color ? ` --color-${key}: ${color};` : null;
|
||||
})
|
||||
.join("\n")}
|
||||
}
|
||||
`,
|
||||
)
|
||||
.join("\n"),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ChartTooltip = RechartsPrimitive.Tooltip;
|
||||
|
||||
const ChartTooltipContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
|
||||
React.ComponentProps<"div"> & {
|
||||
hideLabel?: boolean;
|
||||
hideIndicator?: boolean;
|
||||
indicator?: "line" | "dot" | "dashed";
|
||||
nameKey?: string;
|
||||
labelKey?: string;
|
||||
}
|
||||
>(
|
||||
(
|
||||
{
|
||||
active,
|
||||
payload,
|
||||
className,
|
||||
indicator = "dot",
|
||||
hideLabel = false,
|
||||
hideIndicator = false,
|
||||
label,
|
||||
labelFormatter,
|
||||
labelClassName,
|
||||
formatter,
|
||||
color,
|
||||
nameKey,
|
||||
labelKey,
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const { config } = useChart();
|
||||
|
||||
const tooltipLabel = React.useMemo(() => {
|
||||
if (hideLabel || !payload?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [item] = payload;
|
||||
const key = `${labelKey || item.dataKey || item.name || "value"}`;
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
||||
const value =
|
||||
!labelKey && typeof label === "string"
|
||||
? config[label as keyof typeof config]?.label || label
|
||||
: itemConfig?.label;
|
||||
|
||||
if (labelFormatter) {
|
||||
return (
|
||||
<div className={cn("font-medium", labelClassName)}>
|
||||
{labelFormatter(value, payload)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <div className={cn("font-medium", labelClassName)}>{value}</div>;
|
||||
}, [
|
||||
label,
|
||||
labelFormatter,
|
||||
payload,
|
||||
hideLabel,
|
||||
labelClassName,
|
||||
config,
|
||||
labelKey,
|
||||
]);
|
||||
|
||||
if (!active || !payload?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const nestLabel = payload.length === 1 && indicator !== "dot";
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-neutral-200 border-neutral-200/50 bg-white px-2.5 py-1.5 text-xs shadow-xl dark:border-neutral-800 dark:border-neutral-800/50 dark:bg-neutral-950",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{!nestLabel ? tooltipLabel : null}
|
||||
<div className="grid gap-1.5">
|
||||
{payload.map((item, index) => {
|
||||
const key = `${nameKey || item.name || item.dataKey || "value"}`;
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
||||
const indicatorColor = color || item.payload.fill || item.color;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.dataKey}
|
||||
className={cn(
|
||||
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-neutral-500 dark:[&>svg]:text-neutral-400",
|
||||
indicator === "dot" && "items-center",
|
||||
)}
|
||||
>
|
||||
{formatter && item?.value !== undefined && item.name ? (
|
||||
formatter(item.value, item.name, item, index, item.payload)
|
||||
) : (
|
||||
<>
|
||||
{itemConfig?.icon ? (
|
||||
<itemConfig.icon />
|
||||
) : (
|
||||
!hideIndicator && (
|
||||
<div
|
||||
className={cn(
|
||||
"shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
|
||||
{
|
||||
"h-2.5 w-2.5": indicator === "dot",
|
||||
"w-1": indicator === "line",
|
||||
"w-0 border-[1.5px] border-dashed bg-transparent":
|
||||
indicator === "dashed",
|
||||
"my-0.5": nestLabel && indicator === "dashed",
|
||||
},
|
||||
)}
|
||||
style={
|
||||
{
|
||||
"--color-bg": indicatorColor,
|
||||
"--color-border": indicatorColor,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-1 justify-between leading-none",
|
||||
nestLabel ? "items-end" : "items-center",
|
||||
)}
|
||||
>
|
||||
<div className="grid gap-1.5">
|
||||
{nestLabel ? tooltipLabel : null}
|
||||
<span className="text-neutral-500 dark:text-neutral-400">
|
||||
{itemConfig?.label || item.name}
|
||||
</span>
|
||||
</div>
|
||||
{item.value && (
|
||||
<span className="font-mono font-medium tabular-nums text-neutral-950 dark:text-neutral-50">
|
||||
{item.value.toLocaleString()}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
ChartTooltipContent.displayName = "ChartTooltip";
|
||||
|
||||
const ChartLegend = RechartsPrimitive.Legend;
|
||||
|
||||
const ChartLegendContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.ComponentProps<"div"> &
|
||||
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
|
||||
hideIcon?: boolean;
|
||||
nameKey?: string;
|
||||
}
|
||||
>(
|
||||
(
|
||||
{ className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
|
||||
ref,
|
||||
) => {
|
||||
const { config } = useChart();
|
||||
|
||||
if (!payload?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex items-center justify-center gap-4",
|
||||
verticalAlign === "top" ? "pb-3" : "pt-3",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{payload.map((item) => {
|
||||
const key = `${nameKey || item.dataKey || "value"}`;
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.value}
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-neutral-500 dark:[&>svg]:text-neutral-400",
|
||||
)}
|
||||
>
|
||||
{itemConfig?.icon && !hideIcon ? (
|
||||
<itemConfig.icon />
|
||||
) : (
|
||||
<div
|
||||
className="h-2 w-2 shrink-0 rounded-[2px]"
|
||||
style={{
|
||||
backgroundColor: item.color,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{itemConfig?.label}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
ChartLegendContent.displayName = "ChartLegend";
|
||||
|
||||
// Helper to extract item config from a payload.
|
||||
function getPayloadConfigFromPayload(
|
||||
config: ChartConfig,
|
||||
payload: unknown,
|
||||
key: string,
|
||||
) {
|
||||
if (typeof payload !== "object" || payload === null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const payloadPayload =
|
||||
"payload" in payload &&
|
||||
typeof payload.payload === "object" &&
|
||||
payload.payload !== null
|
||||
? payload.payload
|
||||
: undefined;
|
||||
|
||||
let configLabelKey: string = key;
|
||||
|
||||
if (
|
||||
key in payload &&
|
||||
typeof payload[key as keyof typeof payload] === "string"
|
||||
) {
|
||||
configLabelKey = payload[key as keyof typeof payload] as string;
|
||||
} else if (
|
||||
payloadPayload &&
|
||||
key in payloadPayload &&
|
||||
typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
|
||||
) {
|
||||
configLabelKey = payloadPayload[key as keyof typeof payloadPayload] as string;
|
||||
}
|
||||
|
||||
return configLabelKey in config
|
||||
? config[configLabelKey]
|
||||
: config[key as keyof typeof config];
|
||||
}
|
||||
|
||||
export {
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
ChartLegend,
|
||||
ChartLegendContent,
|
||||
ChartStyle,
|
||||
};
|
||||
28
cofounder/dashboard/src/components/ui/checkbox.tsx
Normal file
28
cofounder/dashboard/src/components/ui/checkbox.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import * as React from "react";
|
||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
||||
import { Check } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Checkbox = React.forwardRef<
|
||||
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CheckboxPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"peer h-4 w-4 shrink-0 rounded-sm border border-neutral-200 border-neutral-900 ring-offset-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-neutral-900 data-[state=checked]:text-neutral-50 dark:border-neutral-800 dark:border-neutral-50 dark:ring-offset-neutral-950 dark:focus-visible:ring-neutral-300 dark:data-[state=checked]:bg-neutral-50 dark:data-[state=checked]:text-neutral-900",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<CheckboxPrimitive.Indicator
|
||||
className={cn("flex items-center justify-center text-current")}
|
||||
>
|
||||
<Check className="h-4 w-4" />
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Root>
|
||||
));
|
||||
Checkbox.displayName = CheckboxPrimitive.Root.displayName;
|
||||
|
||||
export { Checkbox };
|
||||
9
cofounder/dashboard/src/components/ui/collapsible.tsx
Normal file
9
cofounder/dashboard/src/components/ui/collapsible.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
|
||||
|
||||
const Collapsible = CollapsiblePrimitive.Root;
|
||||
|
||||
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
|
||||
|
||||
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
|
||||
|
||||
export { Collapsible, CollapsibleTrigger, CollapsibleContent };
|
||||
153
cofounder/dashboard/src/components/ui/command.tsx
Normal file
153
cofounder/dashboard/src/components/ui/command.tsx
Normal file
@@ -0,0 +1,153 @@
|
||||
import * as React from "react";
|
||||
import { type DialogProps } from "@radix-ui/react-dialog";
|
||||
import { Command as CommandPrimitive } from "cmdk";
|
||||
import { Search } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Dialog, DialogContent } from "@/components/ui/dialog";
|
||||
|
||||
const Command = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CommandPrimitive
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-full w-full flex-col overflow-hidden rounded-md bg-white text-neutral-950 dark:bg-neutral-950 dark:text-neutral-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Command.displayName = CommandPrimitive.displayName;
|
||||
|
||||
interface CommandDialogProps extends DialogProps {}
|
||||
|
||||
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
|
||||
return (
|
||||
<Dialog {...props}>
|
||||
<DialogContent className="overflow-hidden p-0 shadow-lg">
|
||||
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-neutral-500 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5 dark:[&_[cmdk-group-heading]]:text-neutral-400">
|
||||
{children}
|
||||
</Command>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
const CommandInput = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.Input>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
|
||||
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
<CommandPrimitive.Input
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-neutral-500 disabled:cursor-not-allowed disabled:opacity-50 dark:placeholder:text-neutral-400",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
|
||||
CommandInput.displayName = CommandPrimitive.Input.displayName;
|
||||
|
||||
const CommandList = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CommandPrimitive.List
|
||||
ref={ref}
|
||||
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
CommandList.displayName = CommandPrimitive.List.displayName;
|
||||
|
||||
const CommandEmpty = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.Empty>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
|
||||
>((props, ref) => (
|
||||
<CommandPrimitive.Empty
|
||||
ref={ref}
|
||||
className="py-6 text-center text-sm"
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
|
||||
|
||||
const CommandGroup = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.Group>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CommandPrimitive.Group
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"overflow-hidden p-1 text-neutral-950 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-neutral-500 dark:text-neutral-50 dark:[&_[cmdk-group-heading]]:text-neutral-400",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
CommandGroup.displayName = CommandPrimitive.Group.displayName;
|
||||
|
||||
const CommandSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CommandPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 h-px bg-neutral-200 dark:bg-neutral-800", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
|
||||
|
||||
const CommandItem = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CommandPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-neutral-100 data-[selected=true]:text-neutral-900 data-[disabled=true]:opacity-50 dark:data-[selected='true']:bg-neutral-800 dark:data-[selected=true]:text-neutral-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
CommandItem.displayName = CommandPrimitive.Item.displayName;
|
||||
|
||||
const CommandShortcut = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"ml-auto text-xs tracking-widest text-neutral-500 dark:text-neutral-400",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
CommandShortcut.displayName = "CommandShortcut";
|
||||
|
||||
export {
|
||||
Command,
|
||||
CommandDialog,
|
||||
CommandInput,
|
||||
CommandList,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandItem,
|
||||
CommandShortcut,
|
||||
CommandSeparator,
|
||||
};
|
||||
201
cofounder/dashboard/src/components/ui/context-menu.tsx
Normal file
201
cofounder/dashboard/src/components/ui/context-menu.tsx
Normal file
@@ -0,0 +1,201 @@
|
||||
import * as React from "react";
|
||||
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
|
||||
import { Check, ChevronRight, Circle } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const ContextMenu = ContextMenuPrimitive.Root;
|
||||
|
||||
const ContextMenuTrigger = ContextMenuPrimitive.Trigger;
|
||||
|
||||
const ContextMenuGroup = ContextMenuPrimitive.Group;
|
||||
|
||||
const ContextMenuPortal = ContextMenuPrimitive.Portal;
|
||||
|
||||
const ContextMenuSub = ContextMenuPrimitive.Sub;
|
||||
|
||||
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
|
||||
|
||||
const ContextMenuSubTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
|
||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
|
||||
inset?: boolean;
|
||||
}
|
||||
>(({ className, inset, children, ...props }, ref) => (
|
||||
<ContextMenuPrimitive.SubTrigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-neutral-100 focus:text-neutral-900 data-[state=open]:bg-neutral-100 data-[state=open]:text-neutral-900 dark:focus:bg-neutral-800 dark:focus:text-neutral-50 dark:data-[state=open]:bg-neutral-800 dark:data-[state=open]:text-neutral-50",
|
||||
inset && "pl-8",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronRight className="ml-auto h-4 w-4" />
|
||||
</ContextMenuPrimitive.SubTrigger>
|
||||
));
|
||||
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName;
|
||||
|
||||
const ContextMenuSubContent = React.forwardRef<
|
||||
React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
|
||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ContextMenuPrimitive.SubContent
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border border-neutral-200 bg-white p-1 text-neutral-950 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;
|
||||
|
||||
const ContextMenuContent = React.forwardRef<
|
||||
React.ElementRef<typeof ContextMenuPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ContextMenuPrimitive.Portal>
|
||||
<ContextMenuPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border border-neutral-200 bg-white p-1 text-neutral-950 shadow-md animate-in fade-in-80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</ContextMenuPrimitive.Portal>
|
||||
));
|
||||
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;
|
||||
|
||||
const ContextMenuItem = React.forwardRef<
|
||||
React.ElementRef<typeof ContextMenuPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
|
||||
inset?: boolean;
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<ContextMenuPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
|
||||
inset && "pl-8",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName;
|
||||
|
||||
const ContextMenuCheckboxItem = React.forwardRef<
|
||||
React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
|
||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
|
||||
>(({ className, children, checked, ...props }, ref) => (
|
||||
<ContextMenuPrimitive.CheckboxItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
|
||||
className,
|
||||
)}
|
||||
checked={checked}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<ContextMenuPrimitive.ItemIndicator>
|
||||
<Check className="h-4 w-4" />
|
||||
</ContextMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</ContextMenuPrimitive.CheckboxItem>
|
||||
));
|
||||
ContextMenuCheckboxItem.displayName =
|
||||
ContextMenuPrimitive.CheckboxItem.displayName;
|
||||
|
||||
const ContextMenuRadioItem = React.forwardRef<
|
||||
React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
|
||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<ContextMenuPrimitive.RadioItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<ContextMenuPrimitive.ItemIndicator>
|
||||
<Circle className="h-2 w-2 fill-current" />
|
||||
</ContextMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</ContextMenuPrimitive.RadioItem>
|
||||
));
|
||||
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName;
|
||||
|
||||
const ContextMenuLabel = React.forwardRef<
|
||||
React.ElementRef<typeof ContextMenuPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
|
||||
inset?: boolean;
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<ContextMenuPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"px-2 py-1.5 text-sm font-semibold text-neutral-950 dark:text-neutral-50",
|
||||
inset && "pl-8",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName;
|
||||
|
||||
const ContextMenuSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof ContextMenuPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ContextMenuPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"-mx-1 my-1 h-px bg-neutral-200 dark:bg-neutral-800",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName;
|
||||
|
||||
const ContextMenuShortcut = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"ml-auto text-xs tracking-widest text-neutral-500 dark:text-neutral-400",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
ContextMenuShortcut.displayName = "ContextMenuShortcut";
|
||||
|
||||
export {
|
||||
ContextMenu,
|
||||
ContextMenuTrigger,
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuCheckboxItem,
|
||||
ContextMenuRadioItem,
|
||||
ContextMenuLabel,
|
||||
ContextMenuSeparator,
|
||||
ContextMenuShortcut,
|
||||
ContextMenuGroup,
|
||||
ContextMenuPortal,
|
||||
ContextMenuSub,
|
||||
ContextMenuSubContent,
|
||||
ContextMenuSubTrigger,
|
||||
ContextMenuRadioGroup,
|
||||
};
|
||||
117
cofounder/dashboard/src/components/ui/dialog.tsx
Normal file
117
cofounder/dashboard/src/components/ui/dialog.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
import * as React from "react";
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||
import { X } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Dialog = DialogPrimitive.Root;
|
||||
|
||||
const DialogTrigger = DialogPrimitive.Trigger;
|
||||
|
||||
const DialogPortal = DialogPrimitive.Portal;
|
||||
|
||||
const DialogClose = DialogPrimitive.Close;
|
||||
|
||||
const DialogOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
||||
|
||||
const DialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-neutral-200 bg-white p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg dark:border-neutral-800 dark:bg-neutral-950",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-white transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-neutral-950 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-neutral-100 data-[state=open]:text-neutral-500 dark:ring-offset-neutral-950 dark:focus:ring-neutral-300 dark:data-[state=open]:bg-neutral-800 dark:data-[state=open]:text-neutral-400">
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
));
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
||||
|
||||
const DialogHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col space-y-1.5 text-center sm:text-left",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
DialogHeader.displayName = "DialogHeader";
|
||||
|
||||
const DialogFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
DialogFooter.displayName = "DialogFooter";
|
||||
|
||||
const DialogTitle = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn("text-lg font-semibold leading-none tracking-tight", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DialogTitle.displayName = DialogPrimitive.Title.displayName;
|
||||
|
||||
const DialogDescription = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-neutral-500 dark:text-neutral-400", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
||||
|
||||
export {
|
||||
Dialog,
|
||||
DialogPortal,
|
||||
DialogOverlay,
|
||||
DialogClose,
|
||||
DialogTrigger,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
};
|
||||
110
cofounder/dashboard/src/components/ui/drawer.tsx
Normal file
110
cofounder/dashboard/src/components/ui/drawer.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import * as React from "react";
|
||||
import { Drawer as DrawerPrimitive } from "vaul";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Drawer = ({
|
||||
shouldScaleBackground = true,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
|
||||
<DrawerPrimitive.Root
|
||||
shouldScaleBackground={shouldScaleBackground}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
Drawer.displayName = "Drawer";
|
||||
|
||||
const DrawerTrigger = DrawerPrimitive.Trigger;
|
||||
|
||||
const DrawerPortal = DrawerPrimitive.Portal;
|
||||
|
||||
const DrawerClose = DrawerPrimitive.Close;
|
||||
|
||||
const DrawerOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof DrawerPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DrawerPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn("fixed inset-0 z-50 bg-black/80", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
|
||||
|
||||
const DrawerContent = React.forwardRef<
|
||||
React.ElementRef<typeof DrawerPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DrawerPortal>
|
||||
<DrawerOverlay />
|
||||
<DrawerPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border border-neutral-200 bg-white dark:border-neutral-800 dark:bg-neutral-950",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-neutral-100 dark:bg-neutral-800" />
|
||||
{children}
|
||||
</DrawerPrimitive.Content>
|
||||
</DrawerPortal>
|
||||
));
|
||||
DrawerContent.displayName = "DrawerContent";
|
||||
|
||||
const DrawerHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
DrawerHeader.displayName = "DrawerHeader";
|
||||
|
||||
const DrawerFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div className={cn("mt-auto flex flex-col gap-2 p-4", className)} {...props} />
|
||||
);
|
||||
DrawerFooter.displayName = "DrawerFooter";
|
||||
|
||||
const DrawerTitle = React.forwardRef<
|
||||
React.ElementRef<typeof DrawerPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DrawerPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn("text-lg font-semibold leading-none tracking-tight", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
|
||||
|
||||
const DrawerDescription = React.forwardRef<
|
||||
React.ElementRef<typeof DrawerPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DrawerPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-neutral-500 dark:text-neutral-400", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DrawerDescription.displayName = DrawerPrimitive.Description.displayName;
|
||||
|
||||
export {
|
||||
Drawer,
|
||||
DrawerPortal,
|
||||
DrawerOverlay,
|
||||
DrawerTrigger,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerHeader,
|
||||
DrawerFooter,
|
||||
DrawerTitle,
|
||||
DrawerDescription,
|
||||
};
|
||||
201
cofounder/dashboard/src/components/ui/dropdown-menu.tsx
Normal file
201
cofounder/dashboard/src/components/ui/dropdown-menu.tsx
Normal file
@@ -0,0 +1,201 @@
|
||||
import * as React from "react";
|
||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
||||
import { Check, ChevronRight, Circle } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const DropdownMenu = DropdownMenuPrimitive.Root;
|
||||
|
||||
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
|
||||
|
||||
const DropdownMenuGroup = DropdownMenuPrimitive.Group;
|
||||
|
||||
const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
|
||||
|
||||
const DropdownMenuSub = DropdownMenuPrimitive.Sub;
|
||||
|
||||
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
|
||||
|
||||
const DropdownMenuSubTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
||||
inset?: boolean;
|
||||
}
|
||||
>(({ className, inset, children, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-neutral-100 data-[state=open]:bg-neutral-100 dark:focus:bg-neutral-800 dark:data-[state=open]:bg-neutral-800",
|
||||
inset && "pl-8",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronRight className="ml-auto h-4 w-4" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
));
|
||||
DropdownMenuSubTrigger.displayName =
|
||||
DropdownMenuPrimitive.SubTrigger.displayName;
|
||||
|
||||
const DropdownMenuSubContent = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border border-neutral-200 bg-white p-1 text-neutral-950 shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DropdownMenuSubContent.displayName =
|
||||
DropdownMenuPrimitive.SubContent.displayName;
|
||||
|
||||
const DropdownMenuContent = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border border-neutral-200 bg-white p-1 text-neutral-950 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
));
|
||||
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
|
||||
|
||||
const DropdownMenuItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
||||
inset?: boolean;
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
|
||||
inset && "pl-8",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
|
||||
|
||||
const DropdownMenuCheckboxItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
|
||||
>(({ className, children, checked, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
|
||||
className,
|
||||
)}
|
||||
checked={checked}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<Check className="h-4 w-4" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
));
|
||||
DropdownMenuCheckboxItem.displayName =
|
||||
DropdownMenuPrimitive.CheckboxItem.displayName;
|
||||
|
||||
const DropdownMenuRadioItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<Circle className="h-2 w-2 fill-current" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
));
|
||||
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
|
||||
|
||||
const DropdownMenuLabel = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
||||
inset?: boolean;
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"px-2 py-1.5 text-sm font-semibold",
|
||||
inset && "pl-8",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
|
||||
|
||||
const DropdownMenuSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"-mx-1 my-1 h-px bg-neutral-100 dark:bg-neutral-800",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
|
||||
|
||||
const DropdownMenuShortcut = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||
return (
|
||||
<span
|
||||
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
|
||||
|
||||
export {
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuRadioGroup,
|
||||
};
|
||||
177
cofounder/dashboard/src/components/ui/form.tsx
Normal file
177
cofounder/dashboard/src/components/ui/form.tsx
Normal file
@@ -0,0 +1,177 @@
|
||||
import * as React from "react";
|
||||
import * as LabelPrimitive from "@radix-ui/react-label";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import {
|
||||
Controller,
|
||||
ControllerProps,
|
||||
FieldPath,
|
||||
FieldValues,
|
||||
FormProvider,
|
||||
useFormContext,
|
||||
} from "react-hook-form";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
||||
const Form = FormProvider;
|
||||
|
||||
type FormFieldContextValue<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
> = {
|
||||
name: TName;
|
||||
};
|
||||
|
||||
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
||||
{} as FormFieldContextValue,
|
||||
);
|
||||
|
||||
const FormField = <
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
>({
|
||||
...props
|
||||
}: ControllerProps<TFieldValues, TName>) => {
|
||||
return (
|
||||
<FormFieldContext.Provider value={{ name: props.name }}>
|
||||
<Controller {...props} />
|
||||
</FormFieldContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const useFormField = () => {
|
||||
const fieldContext = React.useContext(FormFieldContext);
|
||||
const itemContext = React.useContext(FormItemContext);
|
||||
const { getFieldState, formState } = useFormContext();
|
||||
|
||||
const fieldState = getFieldState(fieldContext.name, formState);
|
||||
|
||||
if (!fieldContext) {
|
||||
throw new Error("useFormField should be used within <FormField>");
|
||||
}
|
||||
|
||||
const { id } = itemContext;
|
||||
|
||||
return {
|
||||
id,
|
||||
name: fieldContext.name,
|
||||
formItemId: `${id}-form-item`,
|
||||
formDescriptionId: `${id}-form-item-description`,
|
||||
formMessageId: `${id}-form-item-message`,
|
||||
...fieldState,
|
||||
};
|
||||
};
|
||||
|
||||
type FormItemContextValue = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
const FormItemContext = React.createContext<FormItemContextValue>(
|
||||
{} as FormItemContextValue,
|
||||
);
|
||||
|
||||
const FormItem = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const id = React.useId();
|
||||
|
||||
return (
|
||||
<FormItemContext.Provider value={{ id }}>
|
||||
<div ref={ref} className={cn("space-y-2", className)} {...props} />
|
||||
</FormItemContext.Provider>
|
||||
);
|
||||
});
|
||||
FormItem.displayName = "FormItem";
|
||||
|
||||
const FormLabel = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { error, formItemId } = useFormField();
|
||||
|
||||
return (
|
||||
<Label
|
||||
ref={ref}
|
||||
className={cn(error && "text-red-500 dark:text-red-900", className)}
|
||||
htmlFor={formItemId}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
FormLabel.displayName = "FormLabel";
|
||||
|
||||
const FormControl = React.forwardRef<
|
||||
React.ElementRef<typeof Slot>,
|
||||
React.ComponentPropsWithoutRef<typeof Slot>
|
||||
>(({ ...props }, ref) => {
|
||||
const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
|
||||
|
||||
return (
|
||||
<Slot
|
||||
ref={ref}
|
||||
id={formItemId}
|
||||
aria-describedby={
|
||||
!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`
|
||||
}
|
||||
aria-invalid={!!error}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
FormControl.displayName = "FormControl";
|
||||
|
||||
const FormDescription = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { formDescriptionId } = useFormField();
|
||||
|
||||
return (
|
||||
<p
|
||||
ref={ref}
|
||||
id={formDescriptionId}
|
||||
className={cn("text-sm text-neutral-500 dark:text-neutral-400", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
FormDescription.displayName = "FormDescription";
|
||||
|
||||
const FormMessage = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, children, ...props }, ref) => {
|
||||
const { error, formMessageId } = useFormField();
|
||||
const body = error ? String(error?.message) : children;
|
||||
|
||||
if (!body) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<p
|
||||
ref={ref}
|
||||
id={formMessageId}
|
||||
className={cn(
|
||||
"text-sm font-medium text-red-500 dark:text-red-900",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{body}
|
||||
</p>
|
||||
);
|
||||
});
|
||||
FormMessage.displayName = "FormMessage";
|
||||
|
||||
export {
|
||||
useFormField,
|
||||
Form,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormMessage,
|
||||
FormField,
|
||||
};
|
||||
27
cofounder/dashboard/src/components/ui/hover-card.tsx
Normal file
27
cofounder/dashboard/src/components/ui/hover-card.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import * as React from "react";
|
||||
import * as HoverCardPrimitive from "@radix-ui/react-hover-card";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const HoverCard = HoverCardPrimitive.Root;
|
||||
|
||||
const HoverCardTrigger = HoverCardPrimitive.Trigger;
|
||||
|
||||
const HoverCardContent = React.forwardRef<
|
||||
React.ElementRef<typeof HoverCardPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
|
||||
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
||||
<HoverCardPrimitive.Content
|
||||
ref={ref}
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 w-64 rounded-md border border-neutral-200 bg-white p-4 text-neutral-950 shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName;
|
||||
|
||||
export { HoverCard, HoverCardTrigger, HoverCardContent };
|
||||
70
cofounder/dashboard/src/components/ui/input-otp.tsx
Normal file
70
cofounder/dashboard/src/components/ui/input-otp.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import * as React from "react";
|
||||
import { OTPInput, OTPInputContext } from "input-otp";
|
||||
import { Dot } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const InputOTP = React.forwardRef<
|
||||
React.ElementRef<typeof OTPInput>,
|
||||
React.ComponentPropsWithoutRef<typeof OTPInput>
|
||||
>(({ className, containerClassName, ...props }, ref) => (
|
||||
<OTPInput
|
||||
ref={ref}
|
||||
containerClassName={cn(
|
||||
"flex items-center gap-2 has-[:disabled]:opacity-50",
|
||||
containerClassName,
|
||||
)}
|
||||
className={cn("disabled:cursor-not-allowed", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
InputOTP.displayName = "InputOTP";
|
||||
|
||||
const InputOTPGroup = React.forwardRef<
|
||||
React.ElementRef<"div">,
|
||||
React.ComponentPropsWithoutRef<"div">
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("flex items-center", className)} {...props} />
|
||||
));
|
||||
InputOTPGroup.displayName = "InputOTPGroup";
|
||||
|
||||
const InputOTPSlot = React.forwardRef<
|
||||
React.ElementRef<"div">,
|
||||
React.ComponentPropsWithoutRef<"div"> & { index: number }
|
||||
>(({ index, className, ...props }, ref) => {
|
||||
const inputOTPContext = React.useContext(OTPInputContext);
|
||||
const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index];
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex h-10 w-10 items-center justify-center border-y border-r border-neutral-200 text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md dark:border-neutral-800",
|
||||
isActive &&
|
||||
"z-10 ring-2 ring-neutral-950 ring-offset-white dark:ring-neutral-300 dark:ring-offset-neutral-950",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{char}
|
||||
{hasFakeCaret && (
|
||||
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
|
||||
<div className="h-4 w-px animate-caret-blink bg-neutral-950 duration-1000 dark:bg-neutral-50" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
InputOTPSlot.displayName = "InputOTPSlot";
|
||||
|
||||
const InputOTPSeparator = React.forwardRef<
|
||||
React.ElementRef<"div">,
|
||||
React.ComponentPropsWithoutRef<"div">
|
||||
>(({ ...props }, ref) => (
|
||||
<div ref={ref} role="separator" {...props}>
|
||||
<Dot />
|
||||
</div>
|
||||
));
|
||||
InputOTPSeparator.displayName = "InputOTPSeparator";
|
||||
|
||||
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator };
|
||||
25
cofounder/dashboard/src/components/ui/input.tsx
Normal file
25
cofounder/dashboard/src/components/ui/input.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export interface InputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, type, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-10 w-full rounded-md border border-neutral-200 bg-white px-3 py-2 text-sm ring-offset-white file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-neutral-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-neutral-800 dark:bg-neutral-950 dark:ring-offset-neutral-950 dark:placeholder:text-neutral-400 dark:focus-visible:ring-neutral-300",
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
Input.displayName = "Input";
|
||||
|
||||
export { Input };
|
||||
24
cofounder/dashboard/src/components/ui/label.tsx
Normal file
24
cofounder/dashboard/src/components/ui/label.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import * as React from "react";
|
||||
import * as LabelPrimitive from "@radix-ui/react-label";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const labelVariants = cva(
|
||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
||||
);
|
||||
|
||||
const Label = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||
VariantProps<typeof labelVariants>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<LabelPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(labelVariants(), className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Label.displayName = LabelPrimitive.Root.displayName;
|
||||
|
||||
export { Label };
|
||||
237
cofounder/dashboard/src/components/ui/menubar.tsx
Normal file
237
cofounder/dashboard/src/components/ui/menubar.tsx
Normal file
@@ -0,0 +1,237 @@
|
||||
import * as React from "react";
|
||||
import * as MenubarPrimitive from "@radix-ui/react-menubar";
|
||||
import { Check, ChevronRight, Circle } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const MenubarMenu = MenubarPrimitive.Menu;
|
||||
|
||||
const MenubarGroup = MenubarPrimitive.Group;
|
||||
|
||||
const MenubarPortal = MenubarPrimitive.Portal;
|
||||
|
||||
const MenubarSub = MenubarPrimitive.Sub;
|
||||
|
||||
const MenubarRadioGroup = MenubarPrimitive.RadioGroup;
|
||||
|
||||
const Menubar = React.forwardRef<
|
||||
React.ElementRef<typeof MenubarPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<MenubarPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-10 items-center space-x-1 rounded-md border border-neutral-200 bg-white p-1 dark:border-neutral-800 dark:bg-neutral-950",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Menubar.displayName = MenubarPrimitive.Root.displayName;
|
||||
|
||||
const MenubarTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof MenubarPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<MenubarPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default select-none items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none focus:bg-neutral-100 focus:text-neutral-900 data-[state=open]:bg-neutral-100 data-[state=open]:text-neutral-900 dark:focus:bg-neutral-800 dark:focus:text-neutral-50 dark:data-[state=open]:bg-neutral-800 dark:data-[state=open]:text-neutral-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName;
|
||||
|
||||
const MenubarSubTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof MenubarPrimitive.SubTrigger>,
|
||||
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {
|
||||
inset?: boolean;
|
||||
}
|
||||
>(({ className, inset, children, ...props }, ref) => (
|
||||
<MenubarPrimitive.SubTrigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-neutral-100 focus:text-neutral-900 data-[state=open]:bg-neutral-100 data-[state=open]:text-neutral-900 dark:focus:bg-neutral-800 dark:focus:text-neutral-50 dark:data-[state=open]:bg-neutral-800 dark:data-[state=open]:text-neutral-50",
|
||||
inset && "pl-8",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronRight className="ml-auto h-4 w-4" />
|
||||
</MenubarPrimitive.SubTrigger>
|
||||
));
|
||||
MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName;
|
||||
|
||||
const MenubarSubContent = React.forwardRef<
|
||||
React.ElementRef<typeof MenubarPrimitive.SubContent>,
|
||||
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<MenubarPrimitive.SubContent
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border border-neutral-200 bg-white p-1 text-neutral-950 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName;
|
||||
|
||||
const MenubarContent = React.forwardRef<
|
||||
React.ElementRef<typeof MenubarPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>
|
||||
>(
|
||||
(
|
||||
{ className, align = "start", alignOffset = -4, sideOffset = 8, ...props },
|
||||
ref,
|
||||
) => (
|
||||
<MenubarPrimitive.Portal>
|
||||
<MenubarPrimitive.Content
|
||||
ref={ref}
|
||||
align={align}
|
||||
alignOffset={alignOffset}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 min-w-[12rem] overflow-hidden rounded-md border border-neutral-200 bg-white p-1 text-neutral-950 shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</MenubarPrimitive.Portal>
|
||||
),
|
||||
);
|
||||
MenubarContent.displayName = MenubarPrimitive.Content.displayName;
|
||||
|
||||
const MenubarItem = React.forwardRef<
|
||||
React.ElementRef<typeof MenubarPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {
|
||||
inset?: boolean;
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<MenubarPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
|
||||
inset && "pl-8",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
MenubarItem.displayName = MenubarPrimitive.Item.displayName;
|
||||
|
||||
const MenubarCheckboxItem = React.forwardRef<
|
||||
React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,
|
||||
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>
|
||||
>(({ className, children, checked, ...props }, ref) => (
|
||||
<MenubarPrimitive.CheckboxItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
|
||||
className,
|
||||
)}
|
||||
checked={checked}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<MenubarPrimitive.ItemIndicator>
|
||||
<Check className="h-4 w-4" />
|
||||
</MenubarPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</MenubarPrimitive.CheckboxItem>
|
||||
));
|
||||
MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName;
|
||||
|
||||
const MenubarRadioItem = React.forwardRef<
|
||||
React.ElementRef<typeof MenubarPrimitive.RadioItem>,
|
||||
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<MenubarPrimitive.RadioItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<MenubarPrimitive.ItemIndicator>
|
||||
<Circle className="h-2 w-2 fill-current" />
|
||||
</MenubarPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</MenubarPrimitive.RadioItem>
|
||||
));
|
||||
MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName;
|
||||
|
||||
const MenubarLabel = React.forwardRef<
|
||||
React.ElementRef<typeof MenubarPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {
|
||||
inset?: boolean;
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<MenubarPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"px-2 py-1.5 text-sm font-semibold",
|
||||
inset && "pl-8",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
MenubarLabel.displayName = MenubarPrimitive.Label.displayName;
|
||||
|
||||
const MenubarSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof MenubarPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<MenubarPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"-mx-1 my-1 h-px bg-neutral-100 dark:bg-neutral-800",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName;
|
||||
|
||||
const MenubarShortcut = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"ml-auto text-xs tracking-widest text-neutral-500 dark:text-neutral-400",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
MenubarShortcut.displayname = "MenubarShortcut";
|
||||
|
||||
export {
|
||||
Menubar,
|
||||
MenubarMenu,
|
||||
MenubarTrigger,
|
||||
MenubarContent,
|
||||
MenubarItem,
|
||||
MenubarSeparator,
|
||||
MenubarLabel,
|
||||
MenubarCheckboxItem,
|
||||
MenubarRadioGroup,
|
||||
MenubarRadioItem,
|
||||
MenubarPortal,
|
||||
MenubarSubContent,
|
||||
MenubarSubTrigger,
|
||||
MenubarGroup,
|
||||
MenubarSub,
|
||||
MenubarShortcut,
|
||||
};
|
||||
129
cofounder/dashboard/src/components/ui/navigation-menu.tsx
Normal file
129
cofounder/dashboard/src/components/ui/navigation-menu.tsx
Normal file
@@ -0,0 +1,129 @@
|
||||
import * as React from "react";
|
||||
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu";
|
||||
import { cva } from "class-variance-authority";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const NavigationMenu = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative z-10 flex max-w-max flex-1 items-center justify-center",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<NavigationMenuViewport />
|
||||
</NavigationMenuPrimitive.Root>
|
||||
));
|
||||
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName;
|
||||
|
||||
const NavigationMenuList = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.List
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"group flex flex-1 list-none items-center justify-center space-x-1",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName;
|
||||
|
||||
const NavigationMenuItem = NavigationMenuPrimitive.Item;
|
||||
|
||||
const navigationMenuTriggerStyle = cva(
|
||||
"group inline-flex h-10 w-max items-center justify-center rounded-md bg-white px-4 py-2 text-sm font-medium transition-colors hover:bg-neutral-100 hover:text-neutral-900 focus:bg-neutral-100 focus:text-neutral-900 focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-neutral-100/50 data-[state=open]:bg-neutral-100/50 dark:bg-neutral-950 dark:hover:bg-neutral-800 dark:hover:text-neutral-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50 dark:data-[active]:bg-neutral-800/50 dark:data-[state=open]:bg-neutral-800/50",
|
||||
);
|
||||
|
||||
const NavigationMenuTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(navigationMenuTriggerStyle(), "group", className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
{""}
|
||||
<ChevronDown
|
||||
className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</NavigationMenuPrimitive.Trigger>
|
||||
));
|
||||
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName;
|
||||
|
||||
const NavigationMenuContent = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName;
|
||||
|
||||
const NavigationMenuLink = NavigationMenuPrimitive.Link;
|
||||
|
||||
const NavigationMenuViewport = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div className={cn("absolute left-0 top-full flex justify-center")}>
|
||||
<NavigationMenuPrimitive.Viewport
|
||||
className={cn(
|
||||
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border border-neutral-200 bg-white text-neutral-950 shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)] dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50",
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
NavigationMenuViewport.displayName =
|
||||
NavigationMenuPrimitive.Viewport.displayName;
|
||||
|
||||
const NavigationMenuIndicator = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.Indicator
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-neutral-200 shadow-md dark:bg-neutral-800" />
|
||||
</NavigationMenuPrimitive.Indicator>
|
||||
));
|
||||
NavigationMenuIndicator.displayName =
|
||||
NavigationMenuPrimitive.Indicator.displayName;
|
||||
|
||||
export {
|
||||
navigationMenuTriggerStyle,
|
||||
NavigationMenu,
|
||||
NavigationMenuList,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuContent,
|
||||
NavigationMenuTrigger,
|
||||
NavigationMenuLink,
|
||||
NavigationMenuIndicator,
|
||||
NavigationMenuViewport,
|
||||
};
|
||||
117
cofounder/dashboard/src/components/ui/pagination.tsx
Normal file
117
cofounder/dashboard/src/components/ui/pagination.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
import * as React from "react";
|
||||
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ButtonProps, buttonVariants } from "@/components/ui/button";
|
||||
|
||||
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
|
||||
<nav
|
||||
role="navigation"
|
||||
aria-label="pagination"
|
||||
className={cn("mx-auto flex w-full justify-center", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
Pagination.displayName = "Pagination";
|
||||
|
||||
const PaginationContent = React.forwardRef<
|
||||
HTMLUListElement,
|
||||
React.ComponentProps<"ul">
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ul
|
||||
ref={ref}
|
||||
className={cn("flex flex-row items-center gap-1", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
PaginationContent.displayName = "PaginationContent";
|
||||
|
||||
const PaginationItem = React.forwardRef<
|
||||
HTMLLIElement,
|
||||
React.ComponentProps<"li">
|
||||
>(({ className, ...props }, ref) => (
|
||||
<li ref={ref} className={cn("", className)} {...props} />
|
||||
));
|
||||
PaginationItem.displayName = "PaginationItem";
|
||||
|
||||
type PaginationLinkProps = {
|
||||
isActive?: boolean;
|
||||
} & Pick<ButtonProps, "size"> &
|
||||
React.ComponentProps<"a">;
|
||||
|
||||
const PaginationLink = ({
|
||||
className,
|
||||
isActive,
|
||||
size = "icon",
|
||||
...props
|
||||
}: PaginationLinkProps) => (
|
||||
<a
|
||||
aria-current={isActive ? "page" : undefined}
|
||||
className={cn(
|
||||
buttonVariants({
|
||||
variant: isActive ? "outline" : "ghost",
|
||||
size,
|
||||
}),
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
PaginationLink.displayName = "PaginationLink";
|
||||
|
||||
const PaginationPrevious = ({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof PaginationLink>) => (
|
||||
<PaginationLink
|
||||
aria-label="Go to previous page"
|
||||
size="default"
|
||||
className={cn("gap-1 pl-2.5", className)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
<span>Previous</span>
|
||||
</PaginationLink>
|
||||
);
|
||||
PaginationPrevious.displayName = "PaginationPrevious";
|
||||
|
||||
const PaginationNext = ({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof PaginationLink>) => (
|
||||
<PaginationLink
|
||||
aria-label="Go to next page"
|
||||
size="default"
|
||||
className={cn("gap-1 pr-2.5", className)}
|
||||
{...props}
|
||||
>
|
||||
<span>Next</span>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</PaginationLink>
|
||||
);
|
||||
PaginationNext.displayName = "PaginationNext";
|
||||
|
||||
const PaginationEllipsis = ({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"span">) => (
|
||||
<span
|
||||
aria-hidden
|
||||
className={cn("flex h-9 w-9 items-center justify-center", className)}
|
||||
{...props}
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
<span className="sr-only">More pages</span>
|
||||
</span>
|
||||
);
|
||||
PaginationEllipsis.displayName = "PaginationEllipsis";
|
||||
|
||||
export {
|
||||
Pagination,
|
||||
PaginationContent,
|
||||
PaginationEllipsis,
|
||||
PaginationItem,
|
||||
PaginationLink,
|
||||
PaginationNext,
|
||||
PaginationPrevious,
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user