Compare commits
34 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
bcbbe5abfd | ||
|
15426cac57 | ||
|
3b8c9e12e4 | ||
|
1f4e70788e | ||
|
b0d444980a | ||
|
cfa9485e97 | ||
|
960a104011 | ||
|
59880fcd6e | ||
|
92584a3ea6 | ||
|
cfc0ae6cf4 | ||
|
e4f4bcfdcc | ||
|
108fa4ff1f | ||
|
103faf1d09 | ||
|
2c7527055f | ||
|
bde591006e | ||
|
94ffbaf60f | ||
|
e62849ab96 | ||
|
250fb45cb9 | ||
|
49d0690081 | ||
|
f38bf79e54 | ||
|
1eb17a0ac5 | ||
|
996eb4339e | ||
|
5faafd477b | ||
|
b88feee86e | ||
|
ceeb1fbce3 | ||
|
024e30bd99 | ||
|
947f0c6f0d | ||
|
f800008487 | ||
|
1d21d55437 | ||
|
274c945fac | ||
|
a41cdff55f | ||
|
182d527416 | ||
|
f3f91ea16f | ||
|
e8590703f8 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -40,7 +40,6 @@ wrangler.toml
|
|||||||
|
|
||||||
dist
|
dist
|
||||||
backend/server/projects
|
backend/server/projects
|
||||||
backend/database/drizzle
|
|
||||||
|
|
||||||
app.yaml
|
app.yaml
|
||||||
ingressController.yaml
|
ingressController.yaml
|
15
README.md
15
README.md
@ -78,6 +78,8 @@ npx wrangler deploy
|
|||||||
|
|
||||||
### 4. Deploying the database
|
### 4. Deploying the database
|
||||||
|
|
||||||
|
Follow this [guide](https://docs.google.com/document/d/1w5dA5daic_sIYB5Seni1KvnFx51pPV2so6lLdN2xa7Q/edit?usp=sharing) for more info.
|
||||||
|
|
||||||
Create a database:
|
Create a database:
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -174,6 +176,15 @@ Setting up deployments first requires a separate domain (such as gitwit.app, whi
|
|||||||
|
|
||||||
We then deploy Dokku on a separate server, according to this guide: https://dev.to/jamesmurdza/host-your-own-paas-platform-as-a-service-on-amazon-web-services-3f0d
|
We then deploy Dokku on a separate server, according to this guide: https://dev.to/jamesmurdza/host-your-own-paas-platform-as-a-service-on-amazon-web-services-3f0d
|
||||||
|
|
||||||
|
And we install [dokku-daemon](https://github.com/dokku/dokku-daemon) with the following commands:
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://github.com/dokku/dokku-daemon
|
||||||
|
cd dokku-daemon
|
||||||
|
sudo make install
|
||||||
|
systemctl start dokku-daemon
|
||||||
|
```
|
||||||
|
|
||||||
The Sandbox platform connects to the Dokku server via SSH, using SSH keys specifically generated for this connection. The SSH key is stored on the Sandbox server, and the following environment variables are set in /backend/server/.env:
|
The Sandbox platform connects to the Dokku server via SSH, using SSH keys specifically generated for this connection. The SSH key is stored on the Sandbox server, and the following environment variables are set in /backend/server/.env:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -182,6 +193,10 @@ DOKKU_USERNAME=
|
|||||||
DOKKU_KEY=
|
DOKKU_KEY=
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Deploying to AWS
|
||||||
|
|
||||||
|
The backend server and deployments server can be deployed using AWS's EC2 service. See [our video guide](https://www.youtube.com/watch?v=WN8HQnimjmk) on how to do this.
|
||||||
|
|
||||||
## Creating Custom Templates
|
## Creating Custom Templates
|
||||||
|
|
||||||
Anyone can contribute a custom template for integration in Sandbox. Since Sandbox is built on E2B, there is no limitation to what langauge or runtime a Sandbox can use.
|
Anyone can contribute a custom template for integration in Sandbox. Since Sandbox is built on E2B, there is no limitation to what langauge or runtime a Sandbox can use.
|
||||||
|
46
backend/database/drizzle/0000_sudden_wallop.sql
Normal file
46
backend/database/drizzle/0000_sudden_wallop.sql
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
CREATE TABLE `sandbox` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`type` text NOT NULL,
|
||||||
|
`visibility` text,
|
||||||
|
`createdAt` integer DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`user_id` text NOT NULL,
|
||||||
|
`likeCount` integer DEFAULT 0,
|
||||||
|
`viewCount` integer DEFAULT 0,
|
||||||
|
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `sandbox_likes` (
|
||||||
|
`user_id` text NOT NULL,
|
||||||
|
`sandbox_id` text NOT NULL,
|
||||||
|
`createdAt` integer DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY(`sandbox_id`, `user_id`),
|
||||||
|
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action,
|
||||||
|
FOREIGN KEY (`sandbox_id`) REFERENCES `sandbox`(`id`) ON UPDATE no action ON DELETE no action
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `user` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`email` text NOT NULL,
|
||||||
|
`username` text NOT NULL,
|
||||||
|
`avatarUrl` text,
|
||||||
|
`githubToken` text,
|
||||||
|
`createdAt` integer DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`generations` integer DEFAULT 0,
|
||||||
|
`tier` text DEFAULT 'FREE',
|
||||||
|
`tierExpiresAt` integer,
|
||||||
|
`lastResetDate` integer
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `users_to_sandboxes` (
|
||||||
|
`userId` text NOT NULL,
|
||||||
|
`sandboxId` text NOT NULL,
|
||||||
|
`sharedOn` integer,
|
||||||
|
FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action,
|
||||||
|
FOREIGN KEY (`sandboxId`) REFERENCES `sandbox`(`id`) ON UPDATE no action ON DELETE no action
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `sandbox_id_unique` ON `sandbox` (`id`);--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `user_id_unique` ON `user` (`id`);--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `user_username_unique` ON `user` (`username`);
|
3
backend/database/drizzle/0001_dusty_komodo.sql
Normal file
3
backend/database/drizzle/0001_dusty_komodo.sql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
ALTER TABLE user ADD `bio` text;--> statement-breakpoint
|
||||||
|
ALTER TABLE user ADD `personalWebsite` text;--> statement-breakpoint
|
||||||
|
ALTER TABLE user ADD `links` text DEFAULT '[]';
|
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE sandbox ADD `containerId` text;
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"version": "5",
|
"version": "5",
|
||||||
"dialect": "sqlite",
|
"dialect": "sqlite",
|
||||||
"id": "1288b006-6410-4b1c-8c96-d9797878a116",
|
"id": "4ada398d-7e4e-448f-8cea-a10b4d844600",
|
||||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||||
"tables": {
|
"tables": {
|
||||||
"sandbox": {
|
"sandbox": {
|
||||||
@ -94,6 +94,72 @@
|
|||||||
"compositePrimaryKeys": {},
|
"compositePrimaryKeys": {},
|
||||||
"uniqueConstraints": {}
|
"uniqueConstraints": {}
|
||||||
},
|
},
|
||||||
|
"sandbox_likes": {
|
||||||
|
"name": "sandbox_likes",
|
||||||
|
"columns": {
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"sandbox_id": {
|
||||||
|
"name": "sandbox_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"sandbox_likes_user_id_user_id_fk": {
|
||||||
|
"name": "sandbox_likes_user_id_user_id_fk",
|
||||||
|
"tableFrom": "sandbox_likes",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"sandbox_likes_sandbox_id_sandbox_id_fk": {
|
||||||
|
"name": "sandbox_likes_sandbox_id_sandbox_id_fk",
|
||||||
|
"tableFrom": "sandbox_likes",
|
||||||
|
"tableTo": "sandbox",
|
||||||
|
"columnsFrom": [
|
||||||
|
"sandbox_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"sandbox_likes_sandbox_id_user_id_pk": {
|
||||||
|
"columns": [
|
||||||
|
"sandbox_id",
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"name": "sandbox_likes_sandbox_id_user_id_pk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"name": "user",
|
"name": "user",
|
||||||
"columns": {
|
"columns": {
|
||||||
@ -132,6 +198,13 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"githubToken": {
|
||||||
|
"name": "githubToken",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
"createdAt": {
|
"createdAt": {
|
||||||
"name": "createdAt",
|
"name": "createdAt",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
353
backend/database/drizzle/meta/0001_snapshot.json
Normal file
353
backend/database/drizzle/meta/0001_snapshot.json
Normal file
@ -0,0 +1,353 @@
|
|||||||
|
{
|
||||||
|
"version": "5",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"id": "80c0b0b2-bb0e-449a-b447-c21863686f58",
|
||||||
|
"prevId": "4ada398d-7e4e-448f-8cea-a10b4d844600",
|
||||||
|
"tables": {
|
||||||
|
"sandbox": {
|
||||||
|
"name": "sandbox",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"visibility": {
|
||||||
|
"name": "visibility",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"likeCount": {
|
||||||
|
"name": "likeCount",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"viewCount": {
|
||||||
|
"name": "viewCount",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"sandbox_id_unique": {
|
||||||
|
"name": "sandbox_id_unique",
|
||||||
|
"columns": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"sandbox_user_id_user_id_fk": {
|
||||||
|
"name": "sandbox_user_id_user_id_fk",
|
||||||
|
"tableFrom": "sandbox",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"sandbox_likes": {
|
||||||
|
"name": "sandbox_likes",
|
||||||
|
"columns": {
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"sandbox_id": {
|
||||||
|
"name": "sandbox_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"sandbox_likes_user_id_user_id_fk": {
|
||||||
|
"name": "sandbox_likes_user_id_user_id_fk",
|
||||||
|
"tableFrom": "sandbox_likes",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"sandbox_likes_sandbox_id_sandbox_id_fk": {
|
||||||
|
"name": "sandbox_likes_sandbox_id_sandbox_id_fk",
|
||||||
|
"tableFrom": "sandbox_likes",
|
||||||
|
"tableTo": "sandbox",
|
||||||
|
"columnsFrom": [
|
||||||
|
"sandbox_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"sandbox_likes_sandbox_id_user_id_pk": {
|
||||||
|
"columns": [
|
||||||
|
"sandbox_id",
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"name": "sandbox_likes_sandbox_id_user_id_pk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"name": "user",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"name": "username",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"avatarUrl": {
|
||||||
|
"name": "avatarUrl",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"githubToken": {
|
||||||
|
"name": "githubToken",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP"
|
||||||
|
},
|
||||||
|
"generations": {
|
||||||
|
"name": "generations",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"bio": {
|
||||||
|
"name": "bio",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"personalWebsite": {
|
||||||
|
"name": "personalWebsite",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"links": {
|
||||||
|
"name": "links",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'[]'"
|
||||||
|
},
|
||||||
|
"tier": {
|
||||||
|
"name": "tier",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'FREE'"
|
||||||
|
},
|
||||||
|
"tierExpiresAt": {
|
||||||
|
"name": "tierExpiresAt",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"lastResetDate": {
|
||||||
|
"name": "lastResetDate",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"user_id_unique": {
|
||||||
|
"name": "user_id_unique",
|
||||||
|
"columns": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
},
|
||||||
|
"user_username_unique": {
|
||||||
|
"name": "user_username_unique",
|
||||||
|
"columns": [
|
||||||
|
"username"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"users_to_sandboxes": {
|
||||||
|
"name": "users_to_sandboxes",
|
||||||
|
"columns": {
|
||||||
|
"userId": {
|
||||||
|
"name": "userId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"sandboxId": {
|
||||||
|
"name": "sandboxId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"sharedOn": {
|
||||||
|
"name": "sharedOn",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"users_to_sandboxes_userId_user_id_fk": {
|
||||||
|
"name": "users_to_sandboxes_userId_user_id_fk",
|
||||||
|
"tableFrom": "users_to_sandboxes",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"users_to_sandboxes_sandboxId_sandbox_id_fk": {
|
||||||
|
"name": "users_to_sandboxes_sandboxId_sandbox_id_fk",
|
||||||
|
"tableFrom": "users_to_sandboxes",
|
||||||
|
"tableTo": "sandbox",
|
||||||
|
"columnsFrom": [
|
||||||
|
"sandboxId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
}
|
||||||
|
}
|
360
backend/database/drizzle/meta/0002_snapshot.json
Normal file
360
backend/database/drizzle/meta/0002_snapshot.json
Normal file
@ -0,0 +1,360 @@
|
|||||||
|
{
|
||||||
|
"version": "5",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"id": "51abcf01-2921-4885-8058-d1ccd576f3e1",
|
||||||
|
"prevId": "80c0b0b2-bb0e-449a-b447-c21863686f58",
|
||||||
|
"tables": {
|
||||||
|
"sandbox": {
|
||||||
|
"name": "sandbox",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"visibility": {
|
||||||
|
"name": "visibility",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"likeCount": {
|
||||||
|
"name": "likeCount",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"viewCount": {
|
||||||
|
"name": "viewCount",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"containerId": {
|
||||||
|
"name": "containerId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"sandbox_id_unique": {
|
||||||
|
"name": "sandbox_id_unique",
|
||||||
|
"columns": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"sandbox_user_id_user_id_fk": {
|
||||||
|
"name": "sandbox_user_id_user_id_fk",
|
||||||
|
"tableFrom": "sandbox",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"sandbox_likes": {
|
||||||
|
"name": "sandbox_likes",
|
||||||
|
"columns": {
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"sandbox_id": {
|
||||||
|
"name": "sandbox_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"sandbox_likes_user_id_user_id_fk": {
|
||||||
|
"name": "sandbox_likes_user_id_user_id_fk",
|
||||||
|
"tableFrom": "sandbox_likes",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"sandbox_likes_sandbox_id_sandbox_id_fk": {
|
||||||
|
"name": "sandbox_likes_sandbox_id_sandbox_id_fk",
|
||||||
|
"tableFrom": "sandbox_likes",
|
||||||
|
"tableTo": "sandbox",
|
||||||
|
"columnsFrom": [
|
||||||
|
"sandbox_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"sandbox_likes_sandbox_id_user_id_pk": {
|
||||||
|
"columns": [
|
||||||
|
"sandbox_id",
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"name": "sandbox_likes_sandbox_id_user_id_pk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"name": "user",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"name": "username",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"avatarUrl": {
|
||||||
|
"name": "avatarUrl",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"githubToken": {
|
||||||
|
"name": "githubToken",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP"
|
||||||
|
},
|
||||||
|
"generations": {
|
||||||
|
"name": "generations",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"bio": {
|
||||||
|
"name": "bio",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"personalWebsite": {
|
||||||
|
"name": "personalWebsite",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"links": {
|
||||||
|
"name": "links",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'[]'"
|
||||||
|
},
|
||||||
|
"tier": {
|
||||||
|
"name": "tier",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'FREE'"
|
||||||
|
},
|
||||||
|
"tierExpiresAt": {
|
||||||
|
"name": "tierExpiresAt",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"lastResetDate": {
|
||||||
|
"name": "lastResetDate",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"user_id_unique": {
|
||||||
|
"name": "user_id_unique",
|
||||||
|
"columns": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
},
|
||||||
|
"user_username_unique": {
|
||||||
|
"name": "user_username_unique",
|
||||||
|
"columns": [
|
||||||
|
"username"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"users_to_sandboxes": {
|
||||||
|
"name": "users_to_sandboxes",
|
||||||
|
"columns": {
|
||||||
|
"userId": {
|
||||||
|
"name": "userId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"sandboxId": {
|
||||||
|
"name": "sandboxId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"sharedOn": {
|
||||||
|
"name": "sharedOn",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"users_to_sandboxes_userId_user_id_fk": {
|
||||||
|
"name": "users_to_sandboxes_userId_user_id_fk",
|
||||||
|
"tableFrom": "users_to_sandboxes",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"users_to_sandboxes_sandboxId_sandbox_id_fk": {
|
||||||
|
"name": "users_to_sandboxes_sandboxId_sandbox_id_fk",
|
||||||
|
"tableFrom": "users_to_sandboxes",
|
||||||
|
"tableTo": "sandbox",
|
||||||
|
"columnsFrom": [
|
||||||
|
"sandboxId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
}
|
||||||
|
}
|
@ -5,8 +5,22 @@
|
|||||||
{
|
{
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"version": "5",
|
"version": "5",
|
||||||
"when": 1732568535771,
|
"when": 1736155854410,
|
||||||
"tag": "0000_rapid_korath",
|
"tag": "0000_sudden_wallop",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 1,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1736169498666,
|
||||||
|
"tag": "0001_dusty_komodo",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 2,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1736768910615,
|
||||||
|
"tag": "0002_chemical_brother_voodoo",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
1960
backend/database/package-lock.json
generated
1960
backend/database/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -18,7 +18,7 @@
|
|||||||
"drizzle-kit": "^0.20.17",
|
"drizzle-kit": "^0.20.17",
|
||||||
"typescript": "^5.0.4",
|
"typescript": "^5.0.4",
|
||||||
"vitest": "1.3.0",
|
"vitest": "1.3.0",
|
||||||
"wrangler": "^3.86.0"
|
"wrangler": "^3.101.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@paralleldrive/cuid2": "^2.2.2",
|
"@paralleldrive/cuid2": "^2.2.2",
|
||||||
|
@ -5,13 +5,7 @@ import { z } from "zod"
|
|||||||
|
|
||||||
import { and, eq, sql } from "drizzle-orm"
|
import { and, eq, sql } from "drizzle-orm"
|
||||||
import * as schema from "./schema"
|
import * as schema from "./schema"
|
||||||
import {
|
import { Sandbox, sandbox, sandboxLikes, user, usersToSandboxes } from "./schema"
|
||||||
Sandbox,
|
|
||||||
sandbox,
|
|
||||||
sandboxLikes,
|
|
||||||
user,
|
|
||||||
usersToSandboxes,
|
|
||||||
} from "./schema"
|
|
||||||
|
|
||||||
export interface Env {
|
export interface Env {
|
||||||
DB: D1Database
|
DB: D1Database
|
||||||
@ -73,6 +67,7 @@ export default {
|
|||||||
const params = url.searchParams
|
const params = url.searchParams
|
||||||
if (params.has("id")) {
|
if (params.has("id")) {
|
||||||
const id = params.get("id") as string
|
const id = params.get("id") as string
|
||||||
|
await db.delete(sandboxLikes).where(eq(sandboxLikes.sandboxId, id))
|
||||||
await db
|
await db
|
||||||
.delete(usersToSandboxes)
|
.delete(usersToSandboxes)
|
||||||
.where(eq(usersToSandboxes.sandboxId, id))
|
.where(eq(usersToSandboxes.sandboxId, id))
|
||||||
@ -245,6 +240,88 @@ export default {
|
|||||||
|
|
||||||
return success
|
return success
|
||||||
} else return methodNotAllowed
|
} else return methodNotAllowed
|
||||||
|
} else if (path === "/api/sandbox/like") {
|
||||||
|
if (method === "POST") {
|
||||||
|
const likeSchema = z.object({
|
||||||
|
sandboxId: z.string(),
|
||||||
|
userId: z.string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
const body = await request.json()
|
||||||
|
const { sandboxId, userId } = likeSchema.parse(body)
|
||||||
|
|
||||||
|
// Check if user has already liked
|
||||||
|
const existingLike = await db.query.sandboxLikes.findFirst({
|
||||||
|
where: (likes, { and, eq }) =>
|
||||||
|
and(eq(likes.sandboxId, sandboxId), eq(likes.userId, userId)),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (existingLike) {
|
||||||
|
// Unlike
|
||||||
|
await db
|
||||||
|
.delete(sandboxLikes)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(sandboxLikes.sandboxId, sandboxId),
|
||||||
|
eq(sandboxLikes.userId, userId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
await db
|
||||||
|
.update(sandbox)
|
||||||
|
.set({
|
||||||
|
likeCount: sql`${sandbox.likeCount} - 1`,
|
||||||
|
})
|
||||||
|
.where(eq(sandbox.id, sandboxId))
|
||||||
|
|
||||||
|
return json({
|
||||||
|
message: "Unlike successful",
|
||||||
|
liked: false,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Like
|
||||||
|
await db.insert(sandboxLikes).values({
|
||||||
|
sandboxId,
|
||||||
|
userId,
|
||||||
|
createdAt: new Date(),
|
||||||
|
})
|
||||||
|
|
||||||
|
await db
|
||||||
|
.update(sandbox)
|
||||||
|
.set({
|
||||||
|
likeCount: sql`${sandbox.likeCount} + 1`,
|
||||||
|
})
|
||||||
|
.where(eq(sandbox.id, sandboxId))
|
||||||
|
|
||||||
|
return json({
|
||||||
|
message: "Like successful",
|
||||||
|
liked: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return new Response("Invalid request format", { status: 400 })
|
||||||
|
}
|
||||||
|
} else if (method === "GET") {
|
||||||
|
const params = url.searchParams
|
||||||
|
const sandboxId = params.get("sandboxId")
|
||||||
|
const userId = params.get("userId")
|
||||||
|
|
||||||
|
if (!sandboxId || !userId) {
|
||||||
|
return invalidRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
const like = await db.query.sandboxLikes.findFirst({
|
||||||
|
where: (likes, { and, eq }) =>
|
||||||
|
and(eq(likes.sandboxId, sandboxId), eq(likes.userId, userId)),
|
||||||
|
})
|
||||||
|
|
||||||
|
return json({
|
||||||
|
liked: !!like,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return methodNotAllowed
|
||||||
|
}
|
||||||
} else if (path === "/api/user") {
|
} else if (path === "/api/user") {
|
||||||
if (method === "GET") {
|
if (method === "GET") {
|
||||||
const params = url.searchParams
|
const params = url.searchParams
|
||||||
@ -316,6 +393,7 @@ export default {
|
|||||||
email: z.string().email(),
|
email: z.string().email(),
|
||||||
username: z.string(),
|
username: z.string(),
|
||||||
avatarUrl: z.string().optional(),
|
avatarUrl: z.string().optional(),
|
||||||
|
githubToken: z.string().nullable().optional(),
|
||||||
createdAt: z.string().optional(),
|
createdAt: z.string().optional(),
|
||||||
generations: z.number().optional(),
|
generations: z.number().optional(),
|
||||||
tier: z.enum(["FREE", "PRO", "ENTERPRISE"]).optional(),
|
tier: z.enum(["FREE", "PRO", "ENTERPRISE"]).optional(),
|
||||||
@ -331,6 +409,7 @@ export default {
|
|||||||
email,
|
email,
|
||||||
username,
|
username,
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
|
githubToken,
|
||||||
createdAt,
|
createdAt,
|
||||||
generations,
|
generations,
|
||||||
tier,
|
tier,
|
||||||
@ -345,6 +424,7 @@ export default {
|
|||||||
email,
|
email,
|
||||||
username,
|
username,
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
|
githubToken,
|
||||||
createdAt: createdAt ? new Date(createdAt) : new Date(),
|
createdAt: createdAt ? new Date(createdAt) : new Date(),
|
||||||
generations,
|
generations,
|
||||||
tier,
|
tier,
|
||||||
@ -365,9 +445,20 @@ export default {
|
|||||||
const updateUserSchema = z.object({
|
const updateUserSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
name: z.string().optional(),
|
name: z.string().optional(),
|
||||||
|
bio: z.string().optional(),
|
||||||
|
personalWebsite: z.string().optional(),
|
||||||
|
links: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
url: z.string(),
|
||||||
|
platform: z.enum(schema.KNOWN_PLATFORMS),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
email: z.string().email().optional(),
|
email: z.string().email().optional(),
|
||||||
username: z.string().optional(),
|
username: z.string().optional(),
|
||||||
avatarUrl: z.string().optional(),
|
avatarUrl: z.string().optional(),
|
||||||
|
githubToken: z.string().nullable().optional(),
|
||||||
generations: z.number().optional(),
|
generations: z.number().optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -2,6 +2,26 @@ import { createId } from "@paralleldrive/cuid2"
|
|||||||
import { relations, sql } from "drizzle-orm"
|
import { relations, sql } from "drizzle-orm"
|
||||||
import { integer, primaryKey, sqliteTable, text } from "drizzle-orm/sqlite-core"
|
import { integer, primaryKey, sqliteTable, text } from "drizzle-orm/sqlite-core"
|
||||||
|
|
||||||
|
export const KNOWN_PLATFORMS = [
|
||||||
|
"github",
|
||||||
|
"twitter",
|
||||||
|
"instagram",
|
||||||
|
"bluesky",
|
||||||
|
"linkedin",
|
||||||
|
"youtube",
|
||||||
|
"twitch",
|
||||||
|
"discord",
|
||||||
|
"mastodon",
|
||||||
|
"threads",
|
||||||
|
"gitlab",
|
||||||
|
"generic",
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export type KnownPlatform = (typeof KNOWN_PLATFORMS)[number]
|
||||||
|
export type UserLink = {
|
||||||
|
url: string
|
||||||
|
platform: KnownPlatform
|
||||||
|
}
|
||||||
// #region Tables
|
// #region Tables
|
||||||
export const user = sqliteTable("user", {
|
export const user = sqliteTable("user", {
|
||||||
id: text("id")
|
id: text("id")
|
||||||
@ -12,10 +32,14 @@ export const user = sqliteTable("user", {
|
|||||||
email: text("email").notNull(),
|
email: text("email").notNull(),
|
||||||
username: text("username").notNull().unique(),
|
username: text("username").notNull().unique(),
|
||||||
avatarUrl: text("avatarUrl"),
|
avatarUrl: text("avatarUrl"),
|
||||||
|
githubToken: text("githubToken"),
|
||||||
createdAt: integer("createdAt", { mode: "timestamp_ms" }).default(
|
createdAt: integer("createdAt", { mode: "timestamp_ms" }).default(
|
||||||
sql`CURRENT_TIMESTAMP`
|
sql`CURRENT_TIMESTAMP`
|
||||||
),
|
),
|
||||||
generations: integer("generations").default(0),
|
generations: integer("generations").default(0),
|
||||||
|
bio: text("bio"),
|
||||||
|
personalWebsite: text("personalWebsite"),
|
||||||
|
links: text("links", { mode: "json" }).default("[]").$type<UserLink[]>(),
|
||||||
tier: text("tier", { enum: ["FREE", "PRO", "ENTERPRISE"] }).default("FREE"),
|
tier: text("tier", { enum: ["FREE", "PRO", "ENTERPRISE"] }).default("FREE"),
|
||||||
tierExpiresAt: integer("tierExpiresAt"),
|
tierExpiresAt: integer("tierExpiresAt"),
|
||||||
lastResetDate: integer("lastResetDate"),
|
lastResetDate: integer("lastResetDate"),
|
||||||
@ -39,6 +63,7 @@ export const sandbox = sqliteTable("sandbox", {
|
|||||||
.references(() => user.id),
|
.references(() => user.id),
|
||||||
likeCount: integer("likeCount").default(0),
|
likeCount: integer("likeCount").default(0),
|
||||||
viewCount: integer("viewCount").default(0),
|
viewCount: integer("viewCount").default(0),
|
||||||
|
containerId: text("containerId"),
|
||||||
})
|
})
|
||||||
|
|
||||||
export type Sandbox = typeof sandbox.$inferSelect
|
export type Sandbox = typeof sandbox.$inferSelect
|
||||||
|
@ -330,13 +330,15 @@ export class FileManager {
|
|||||||
if (Buffer.byteLength(body, "utf-8") > MAX_BODY_SIZE) {
|
if (Buffer.byteLength(body, "utf-8") > MAX_BODY_SIZE) {
|
||||||
throw new Error("File size too large. Please reduce the file size.")
|
throw new Error("File size too large. Please reduce the file size.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save to remote storage
|
||||||
await RemoteFileStorage.saveFile(this.getRemoteFileId(fileId), body)
|
await RemoteFileStorage.saveFile(this.getRemoteFileId(fileId), body)
|
||||||
|
|
||||||
|
// Update local file data cache
|
||||||
let file = this.fileData.find((f) => f.id === fileId)
|
let file = this.fileData.find((f) => f.id === fileId)
|
||||||
if (file) {
|
if (file) {
|
||||||
file.data = body
|
file.data = body
|
||||||
} else {
|
} else {
|
||||||
// If the file wasn't in our cache, add it
|
|
||||||
file = {
|
file = {
|
||||||
id: fileId,
|
id: fileId,
|
||||||
data: body,
|
data: body,
|
||||||
@ -344,7 +346,49 @@ export class FileManager {
|
|||||||
this.fileData.push(file)
|
this.fileData.push(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.sandbox.files.write(path.posix.join(this.dirName, fileId), body)
|
// Save to sandbox filesystem
|
||||||
|
const filePath = path.posix.join(this.dirName, fileId)
|
||||||
|
await this.sandbox.files.write(filePath, body)
|
||||||
|
|
||||||
|
// Instead of updating the entire file structure, just ensure this file exists in it
|
||||||
|
const parts = fileId.split('/').filter(Boolean)
|
||||||
|
let current = this.files
|
||||||
|
let currentPath = ''
|
||||||
|
|
||||||
|
// Navigate/create the path to the file
|
||||||
|
for (let i = 0; i < parts.length - 1; i++) {
|
||||||
|
currentPath += '/' + parts[i]
|
||||||
|
let folder = current.find(
|
||||||
|
(f) => f.type === 'folder' && f.name === parts[i]
|
||||||
|
) as TFolder
|
||||||
|
|
||||||
|
if (!folder) {
|
||||||
|
folder = {
|
||||||
|
id: currentPath,
|
||||||
|
type: 'folder',
|
||||||
|
name: parts[i],
|
||||||
|
children: [],
|
||||||
|
}
|
||||||
|
current.push(folder)
|
||||||
|
}
|
||||||
|
current = folder.children
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add/update the file in the structure if it doesn't exist
|
||||||
|
const fileName = parts[parts.length - 1]
|
||||||
|
const existingFile = current.find(
|
||||||
|
(f) => f.type === 'file' && f.name === fileName
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!existingFile) {
|
||||||
|
current.push({
|
||||||
|
id: fileId,
|
||||||
|
type: 'file',
|
||||||
|
name: fileName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.refreshFileList?.(this.files)
|
||||||
this.fixPermissions()
|
this.fixPermissions()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,10 @@ export class SSHSocketClient {
|
|||||||
private conn: Client
|
private conn: Client
|
||||||
private config: SSHConfig
|
private config: SSHConfig
|
||||||
private socketPath: string
|
private socketPath: string
|
||||||
private isConnected: boolean = false
|
private _isConnected: boolean = false
|
||||||
|
public get isConnected(): boolean {
|
||||||
|
return this._isConnected
|
||||||
|
}
|
||||||
|
|
||||||
// Constructor initializes the SSH client and sets up configuration
|
// Constructor initializes the SSH client and sets up configuration
|
||||||
constructor(config: SSHConfig, socketPath: string) {
|
constructor(config: SSHConfig, socketPath: string) {
|
||||||
@ -34,7 +37,7 @@ export class SSHSocketClient {
|
|||||||
private closeConnection() {
|
private closeConnection() {
|
||||||
console.log("Closing SSH connection...")
|
console.log("Closing SSH connection...")
|
||||||
this.conn.end()
|
this.conn.end()
|
||||||
this.isConnected = false
|
this._isConnected = false
|
||||||
process.exit(0)
|
process.exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,17 +47,17 @@ export class SSHSocketClient {
|
|||||||
this.conn
|
this.conn
|
||||||
.on("ready", () => {
|
.on("ready", () => {
|
||||||
console.log("SSH connection established")
|
console.log("SSH connection established")
|
||||||
this.isConnected = true
|
this._isConnected = true
|
||||||
resolve()
|
resolve()
|
||||||
})
|
})
|
||||||
.on("error", (err) => {
|
.on("error", (err) => {
|
||||||
console.error("SSH connection error:", err)
|
console.error("SSH connection error:", err)
|
||||||
this.isConnected = false
|
this._isConnected = false
|
||||||
reject(err)
|
reject(err)
|
||||||
})
|
})
|
||||||
.on("close", () => {
|
.on("close", () => {
|
||||||
console.log("SSH connection closed")
|
console.log("SSH connection closed")
|
||||||
this.isConnected = false
|
this._isConnected = false
|
||||||
})
|
})
|
||||||
.connect(this.config)
|
.connect(this.config)
|
||||||
})
|
})
|
||||||
@ -86,10 +89,13 @@ export class SSHSocketClient {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
.on("data", (data: Buffer) => {
|
.on("data", (data: Buffer) => {
|
||||||
|
// Netcat remains open until it is closed, so we close the connection once we receive data.
|
||||||
resolve(data.toString())
|
resolve(data.toString())
|
||||||
|
stream.close()
|
||||||
})
|
})
|
||||||
.stderr.on("data", (data: Buffer) => {
|
.stderr.on("data", (data: Buffer) => {
|
||||||
reject(new Error(data.toString()))
|
reject(new Error(data.toString()))
|
||||||
|
stream.close()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -170,8 +170,20 @@ export class Sandbox {
|
|||||||
|
|
||||||
// Handle checking if an app exists
|
// Handle checking if an app exists
|
||||||
const handleAppExists: SocketHandler = async ({ appName }) => {
|
const handleAppExists: SocketHandler = async ({ appName }) => {
|
||||||
if (!this.dokkuClient)
|
if (!this.dokkuClient) {
|
||||||
throw new Error("Failed to check app existence: No Dokku client")
|
console.log("Failed to check app existence: No Dokku client")
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!this.dokkuClient.isConnected) {
|
||||||
|
console.log(
|
||||||
|
"Failed to check app existence: The Dokku client is not connected"
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
exists: await this.dokkuClient.appExists(appName),
|
exists: await this.dokkuClient.appExists(appName),
|
||||||
|
@ -91,23 +91,17 @@ export default async function CodePage({ params }: { params: { id: string } }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<TerminalProvider>
|
||||||
<div className="overflow-hidden overscroll-none w-screen flex flex-col h-screen bg-background">
|
{/* <Room id={sandboxId}> */}
|
||||||
{/* <Room id={sandboxId}> */}
|
<div className="overflow-hidden overscroll-none w-screen h-screen grid [grid-template-rows:3.5rem_auto] bg-background">
|
||||||
<TerminalProvider>
|
<Navbar
|
||||||
<Navbar
|
userData={userData}
|
||||||
userData={userData}
|
sandboxData={sandboxData}
|
||||||
sandboxData={sandboxData}
|
shared={shared as { id: string; name: string; avatarUrl: string }[]}
|
||||||
shared={
|
/>
|
||||||
shared as { id: string; name: string; avatarUrl: string }[]
|
<CodeEditor userData={userData} sandboxData={sandboxData} />
|
||||||
}
|
|
||||||
/>
|
|
||||||
<div className="w-screen flex grow">
|
|
||||||
<CodeEditor userData={userData} sandboxData={sandboxData} />
|
|
||||||
</div>
|
|
||||||
</TerminalProvider>
|
|
||||||
{/* </Room> */}
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
{/* </Room> */}
|
||||||
|
</TerminalProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 25 KiB |
@ -102,26 +102,27 @@
|
|||||||
.light .gradient-button-bg {
|
.light .gradient-button-bg {
|
||||||
background: radial-gradient(
|
background: radial-gradient(
|
||||||
circle at top,
|
circle at top,
|
||||||
#262626 0%,
|
#f5f5f5 0%,
|
||||||
#f5f5f5 50%
|
/* Very light gray */ #e0e0e0 50% /* Soft gray */
|
||||||
); /* Dark gray -> Light gray */
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
.light .gradient-button {
|
.light .gradient-button {
|
||||||
background: radial-gradient(
|
background: radial-gradient(
|
||||||
circle at bottom,
|
circle at bottom,
|
||||||
hsl(0, 10%, 25%) -10%,
|
hsl(0, 0%, 85%) -10%,
|
||||||
#9d9d9d 50%
|
/* Slightly darker gray */ hsl(0, 0%, 95%) 50% /* Very soft light gray */
|
||||||
); /* Light gray -> Almost white */
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
.light .gradient-button-bg > div:hover {
|
.light .gradient-button-bg > div:hover {
|
||||||
background: radial-gradient(
|
background: radial-gradient(
|
||||||
circle at bottom,
|
circle at bottom,
|
||||||
hsl(0, 10%, 25%) -10%,
|
hsl(0, 0%, 80%) -10%,
|
||||||
#9d9d9d 80%
|
/* Slightly darker gray for hover */ hsl(0, 0%, 90%) 80% /* Softer gray */
|
||||||
); /* Light gray -> Almost white */
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
.inline-decoration::before {
|
.inline-decoration::before {
|
||||||
content: "Generate";
|
content: "Generate";
|
||||||
color: #525252;
|
color: #525252;
|
||||||
|
11
frontend/app/icon.svg
Normal file
11
frontend/app/icon.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 17 KiB |
@ -11,7 +11,22 @@ import "./globals.css"
|
|||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Sandbox",
|
title: "Sandbox",
|
||||||
description: "A collaborative, AI-powered cloud code editing environment",
|
description:
|
||||||
|
"an open-source cloud-based code editing environment with custom AI code generation, live preview, real-time collaboration, and AI chat",
|
||||||
|
openGraph: {
|
||||||
|
type: "website",
|
||||||
|
url: "https://sandbox.gitwit.dev",
|
||||||
|
title: "Sandbox",
|
||||||
|
description:
|
||||||
|
"an open-source cloud-based code editing environment with custom AI code generation, live preview, real-time collaboration, and AI chat",
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
site: "https://sandbox.gitwit.dev",
|
||||||
|
title: "Sandbox by Gitwit",
|
||||||
|
description:
|
||||||
|
"an open-source cloud-based code editing environment with custom AI code generation, live preview, real-time collaboration, and AI chat",
|
||||||
|
creator: "@gitwitdev",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
|
1
frontend/app/opengraph-image.alt.txt
Normal file
1
frontend/app/opengraph-image.alt.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
About Sandbox by Gitwit
|
BIN
frontend/app/opengraph-image.png
Normal file
BIN
frontend/app/opengraph-image.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 465 KiB |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 29 KiB |
@ -9,14 +9,19 @@ import DashboardNavbarSearch from "./search"
|
|||||||
export default function DashboardNavbar({ userData }: { userData: User }) {
|
export default function DashboardNavbar({ userData }: { userData: User }) {
|
||||||
return (
|
return (
|
||||||
<div className=" py-2 px-4 w-full flex items-center justify-between border-b border-border">
|
<div className=" py-2 px-4 w-full flex items-center justify-between border-b border-border">
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-2">
|
||||||
<Link
|
<Link
|
||||||
href="/"
|
href="/"
|
||||||
className="ring-offset-2 ring-offset-background focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none rounded-sm"
|
className="ring-offset-2 ring-offset-background focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none rounded-sm"
|
||||||
>
|
>
|
||||||
<Image src={Logo} alt="Logo" width={36} height={36} />
|
<Image src={Logo} alt="Logo" width={36} height={36} />
|
||||||
</Link>
|
</Link>
|
||||||
<div className="text-sm font-medium flex items-center">Sandbox</div>
|
<h1 className="text-xl">
|
||||||
|
<span className="font-semibold">Sandbox</span>{" "}
|
||||||
|
<span className="text-xs font-medium text-muted-foreground">
|
||||||
|
by gitwit
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<DashboardNavbarSearch />
|
<DashboardNavbarSearch />
|
||||||
|
@ -240,7 +240,7 @@ function ProjectCardComponent({
|
|||||||
>
|
>
|
||||||
<CanvasRevealEffect
|
<CanvasRevealEffect
|
||||||
animationSpeed={3}
|
animationSpeed={3}
|
||||||
containerClassName="bg-black"
|
containerClassName="bg-muted"
|
||||||
colors={colors[type]}
|
colors={colors[type]}
|
||||||
dotSize={2}
|
dotSize={2}
|
||||||
/>
|
/>
|
||||||
|
@ -126,8 +126,8 @@ export default function ChatMessage({
|
|||||||
<div
|
<div
|
||||||
className={`relative p-2 rounded-lg ${
|
className={`relative p-2 rounded-lg ${
|
||||||
message.role === "user"
|
message.role === "user"
|
||||||
? "bg-[#262626] text-white"
|
? "bg-foreground text-background"
|
||||||
: "bg-transparent text-white"
|
: "bg-background text-foreground"
|
||||||
} max-w-full`}
|
} max-w-full`}
|
||||||
>
|
>
|
||||||
{/* Render context tabs */}
|
{/* Render context tabs */}
|
||||||
@ -171,7 +171,7 @@ export default function ChatMessage({
|
|||||||
end: e.target.value.split("\n").length,
|
end: e.target.value.split("\n").length,
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
className="w-full p-2 bg-[#1e1e1e] text-white font-mono text-sm rounded"
|
className="w-full p-2 bg-[#1e1e1e] text-foreground font-mono text-sm rounded"
|
||||||
rows={code.split("\n").length}
|
rows={code.split("\n").length}
|
||||||
style={{
|
style={{
|
||||||
resize: "vertical",
|
resize: "vertical",
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||||
import { useSocket } from "@/context/SocketContext"
|
import { useSocket } from "@/context/SocketContext"
|
||||||
import { TFile } from "@/lib/types"
|
import { TFile } from "@/lib/types"
|
||||||
import { ChevronDown, X } from "lucide-react"
|
import { ChevronDown, X } from "lucide-react"
|
||||||
@ -208,9 +209,9 @@ export default function AIChat({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<ScrollArea
|
||||||
ref={chatContainerRef}
|
ref={chatContainerRef}
|
||||||
className="flex-grow overflow-y-auto p-4 space-y-4 relative"
|
className="flex-grow p-4 space-y-4 relative"
|
||||||
>
|
>
|
||||||
{messages.map((message, messageIndex) => (
|
{messages.map((message, messageIndex) => (
|
||||||
// Render chat message component for each message
|
// Render chat message component for each message
|
||||||
@ -241,7 +242,7 @@ export default function AIChat({
|
|||||||
<ChevronDown className="h-5 w-5" />
|
<ChevronDown className="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</ScrollArea>
|
||||||
<div className="p-4 border-t mb-14">
|
<div className="p-4 border-t mb-14">
|
||||||
{/* Render context tabs component */}
|
{/* Render context tabs component */}
|
||||||
<ContextTabs
|
<ContextTabs
|
||||||
|
@ -62,7 +62,7 @@ export default function CodeEditor({
|
|||||||
//SocketContext functions and effects
|
//SocketContext functions and effects
|
||||||
const { socket, setUserAndSandboxId } = useSocket()
|
const { socket, setUserAndSandboxId } = useSocket()
|
||||||
// theme
|
// theme
|
||||||
const { theme } = useTheme()
|
const { resolvedTheme: theme } = useTheme()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Ensure userData.id and sandboxData.id are available before attempting to connect
|
// Ensure userData.id and sandboxData.id are available before attempting to connect
|
||||||
if (userData.id && sandboxData.id) {
|
if (userData.id && sandboxData.id) {
|
||||||
@ -387,9 +387,16 @@ export default function CodeEditor({
|
|||||||
(mergedCode: string, originalCode: string) => {
|
(mergedCode: string, originalCode: string) => {
|
||||||
if (!editorRef) return
|
if (!editorRef) return
|
||||||
|
|
||||||
|
const model = editorRef.getModel()
|
||||||
|
if (!model) return // Store original content
|
||||||
|
;(model as any).originalContent = originalCode
|
||||||
|
|
||||||
|
// Calculate the full range of the document
|
||||||
|
const fullRange = model.getFullModelRange()
|
||||||
|
|
||||||
|
// Create decorations before applying the edit
|
||||||
const originalLines = originalCode.split("\n")
|
const originalLines = originalCode.split("\n")
|
||||||
const mergedLines = mergedCode.split("\n")
|
const mergedLines = mergedCode.split("\n")
|
||||||
|
|
||||||
const decorations: monaco.editor.IModelDeltaDecoration[] = []
|
const decorations: monaco.editor.IModelDeltaDecoration[] = []
|
||||||
|
|
||||||
for (
|
for (
|
||||||
@ -410,14 +417,16 @@ export default function CodeEditor({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store original content in the model
|
// Execute the edit operation
|
||||||
const model = editorRef.getModel()
|
editorRef.executeEdits("apply-code", [
|
||||||
if (model) {
|
{
|
||||||
;(model as any).originalContent = originalCode
|
range: fullRange,
|
||||||
}
|
text: mergedCode,
|
||||||
editorRef.setValue(mergedCode)
|
forceMoveMarkers: true,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
// Apply decorations
|
// Apply decorations after the edit
|
||||||
const newDecorations = editorRef.createDecorationsCollection(decorations)
|
const newDecorations = editorRef.createDecorationsCollection(decorations)
|
||||||
setMergeDecorationsCollection(newDecorations)
|
setMergeDecorationsCollection(newDecorations)
|
||||||
},
|
},
|
||||||
@ -784,31 +793,32 @@ export default function CodeEditor({
|
|||||||
setGenerate((prev) => ({ ...prev, show: false }))
|
setGenerate((prev) => ({ ...prev, show: false }))
|
||||||
|
|
||||||
// Check if the tab already exists in the list of open tabs
|
// Check if the tab already exists in the list of open tabs
|
||||||
const exists = tabs.find((t) => t.id === tab.id)
|
const existingTab = tabs.find((t) => t.id === tab.id)
|
||||||
setTabs((prev) => {
|
|
||||||
if (exists) {
|
|
||||||
// If the tab exists, make it the active tab
|
|
||||||
setActiveFileId(exists.id)
|
|
||||||
return prev
|
|
||||||
}
|
|
||||||
// If the tab doesn't exist, add it to the list of tabs and make it active
|
|
||||||
return [...prev, tab]
|
|
||||||
})
|
|
||||||
|
|
||||||
// If the file's content is already cached, set it as the active content
|
if (existingTab) {
|
||||||
if (fileContents[tab.id]) {
|
// If the tab exists, just make it active
|
||||||
setActiveFileContent(fileContents[tab.id])
|
setActiveFileId(existingTab.id)
|
||||||
|
if (fileContents[existingTab.id]) {
|
||||||
|
setActiveFileContent(fileContents[existingTab.id])
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, fetch the content of the file and cache it
|
// If the tab doesn't exist, add it to the list and make it active
|
||||||
debouncedGetFile(tab.id, (response: string) => {
|
setTabs((prev) => [...prev, tab])
|
||||||
setFileContents((prev) => ({ ...prev, [tab.id]: response }))
|
|
||||||
setActiveFileContent(response)
|
// Fetch content if not cached
|
||||||
})
|
if (!fileContents[tab.id]) {
|
||||||
|
debouncedGetFile(tab.id, (response: string) => {
|
||||||
|
setFileContents((prev) => ({ ...prev, [tab.id]: response }))
|
||||||
|
setActiveFileContent(response)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setActiveFileContent(fileContents[tab.id])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the editor language based on the file type
|
// Set the editor language based on the file type
|
||||||
setEditorLanguage(processFileType(tab.name))
|
setEditorLanguage(processFileType(tab.name))
|
||||||
// Set the active file ID to the new tab
|
// Set the active file ID
|
||||||
setActiveFileId(tab.id)
|
setActiveFileId(tab.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -970,9 +980,9 @@ export default function CodeEditor({
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="flex max-h-full overflow-hidden">
|
||||||
{/* Copilot DOM elements */}
|
|
||||||
<PreviewProvider>
|
<PreviewProvider>
|
||||||
|
{/* Copilot DOM elements */}
|
||||||
<div ref={generateRef} />
|
<div ref={generateRef} />
|
||||||
<div ref={suggestionRef} className="absolute">
|
<div ref={suggestionRef} className="absolute">
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
@ -1294,7 +1304,7 @@ export default function CodeEditor({
|
|||||||
)}
|
)}
|
||||||
</ResizablePanelGroup>
|
</ResizablePanelGroup>
|
||||||
</PreviewProvider>
|
</PreviewProvider>
|
||||||
</>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,12 +21,14 @@ export default function DeployButtonModal({
|
|||||||
const { deploy, getAppExists } = useTerminal()
|
const { deploy, getAppExists } = useTerminal()
|
||||||
const [isDeploying, setIsDeploying] = useState(false)
|
const [isDeploying, setIsDeploying] = useState(false)
|
||||||
const [isDeployed, setIsDeployed] = useState(false)
|
const [isDeployed, setIsDeployed] = useState(false)
|
||||||
|
const [deployButtonVisible, setDeployButtonEnabled] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkDeployment = async () => {
|
const checkDeployment = async () => {
|
||||||
if (getAppExists) {
|
if (getAppExists) {
|
||||||
const exists = await getAppExists(data.id)
|
const exists = await getAppExists(data.id)
|
||||||
setIsDeployed(exists)
|
setDeployButtonEnabled(exists.success)
|
||||||
|
setIsDeployed((exists.success && exists.exists) ?? false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
checkDeployment()
|
checkDeployment()
|
||||||
@ -50,10 +52,12 @@ export default function DeployButtonModal({
|
|||||||
<>
|
<>
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button variant="outline">
|
{deployButtonVisible && (
|
||||||
<Globe className="w-4 h-4 mr-2" />
|
<Button variant="outline">
|
||||||
Deploy
|
<Globe className="w-4 h-4 mr-2" />
|
||||||
</Button>
|
Deploy
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent
|
<PopoverContent
|
||||||
className="p-4 w-full max-w-xs sm:max-w-sm md:max-w-md lg:max-w-lg xl:max-w-xl rounded-lg shadow-lg"
|
className="p-4 w-full max-w-xs sm:max-w-sm md:max-w-md lg:max-w-lg xl:max-w-xl rounded-lg shadow-lg"
|
||||||
|
@ -9,6 +9,7 @@ import SidebarFolder from "./folder"
|
|||||||
import New from "./new"
|
import New from "./new"
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||||
import { Skeleton } from "@/components/ui/skeleton"
|
import { Skeleton } from "@/components/ui/skeleton"
|
||||||
import { cn, sortFileExplorer } from "@/lib/utils"
|
import { cn, sortFileExplorer } from "@/lib/utils"
|
||||||
import {
|
import {
|
||||||
@ -104,8 +105,8 @@ export default function Sidebar({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-56 select-none flex flex-col text-sm">
|
<div className="h-full w-56 select-none flex flex-col text-sm">
|
||||||
<div className="flex-grow overflow-auto p-2 pb-[84px]">
|
<ScrollArea className="flex-grow overflow-auto px-2 pt-0 pb-4 relative">
|
||||||
<div className="flex w-full items-center justify-between h-8 mb-1">
|
<div className="flex w-full items-center justify-between h-8 pb-1 isolate z-10 sticky pt-2 top-0 bg-background">
|
||||||
<div className="text-muted-foreground">Explorer</div>
|
<div className="text-muted-foreground">Explorer</div>
|
||||||
<div className="flex space-x-1">
|
<div className="flex space-x-1">
|
||||||
<button
|
<button
|
||||||
@ -179,8 +180,8 @@ export default function Sidebar({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ScrollArea>
|
||||||
<div className="fixed bottom-0 w-48 flex flex-col p-2 bg-background">
|
<div className="flex flex-col p-2 bg-background">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
className="w-full justify-start text-sm text-muted-foreground font-normal h-8 px-2 mb-2"
|
className="w-full justify-start text-sm text-muted-foreground font-normal h-8 px-2 mb-2"
|
||||||
|
@ -22,7 +22,7 @@ export default function EditorTerminal({
|
|||||||
setTerm: (term: Terminal) => void
|
setTerm: (term: Terminal) => void
|
||||||
visible: boolean
|
visible: boolean
|
||||||
}) {
|
}) {
|
||||||
const { theme } = useTheme()
|
const { resolvedTheme: theme } = useTheme()
|
||||||
const terminalContainerRef = useRef<ElementRef<"div">>(null)
|
const terminalContainerRef = useRef<ElementRef<"div">>(null)
|
||||||
const fitAddonRef = useRef<FitAddon | null>(null)
|
const fitAddonRef = useRef<FitAddon | null>(null)
|
||||||
|
|
||||||
|
@ -9,19 +9,38 @@ import {
|
|||||||
CardDescription,
|
CardDescription,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card"
|
} from "@/components/ui/card"
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "@/components/ui/form"
|
||||||
import {
|
import {
|
||||||
HoverCard,
|
HoverCard,
|
||||||
HoverCardContent,
|
HoverCardContent,
|
||||||
HoverCardTrigger,
|
HoverCardTrigger,
|
||||||
} from "@/components/ui/hover-card"
|
} from "@/components/ui/hover-card"
|
||||||
import { Label } from "@/components/ui/label"
|
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/components/ui/tooltip"
|
||||||
import { deleteSandbox, updateSandbox, updateUser } from "@/lib/actions"
|
import { deleteSandbox, updateSandbox, updateUser } from "@/lib/actions"
|
||||||
|
import { socialIcons } from "@/lib/data"
|
||||||
|
import { editUserSchema, EditUserSchema } from "@/lib/schema"
|
||||||
import { TIERS } from "@/lib/tiers"
|
import { TIERS } from "@/lib/tiers"
|
||||||
import { SandboxWithLiked, User } from "@/lib/types"
|
import { SandboxWithLiked, User, UserLink } from "@/lib/types"
|
||||||
|
import { cn, parseSocialLink } from "@/lib/utils"
|
||||||
import { useUser } from "@clerk/nextjs"
|
import { useUser } from "@clerk/nextjs"
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
import {
|
import {
|
||||||
Edit,
|
Edit,
|
||||||
|
Globe,
|
||||||
Heart,
|
Heart,
|
||||||
Info,
|
Info,
|
||||||
Loader2,
|
Loader2,
|
||||||
@ -29,17 +48,27 @@ import {
|
|||||||
Package2,
|
Package2,
|
||||||
PlusCircle,
|
PlusCircle,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
|
Trash2,
|
||||||
X,
|
X,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import { useRouter } from "next/navigation"
|
import { useRouter } from "next/navigation"
|
||||||
import { Fragment, useCallback, useEffect, useMemo, useState } from "react"
|
import {
|
||||||
|
Fragment,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
useTransition,
|
||||||
|
} from "react"
|
||||||
import { useFormState, useFormStatus } from "react-dom"
|
import { useFormState, useFormStatus } from "react-dom"
|
||||||
|
import { useFieldArray, useForm } from "react-hook-form"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
import Avatar from "../ui/avatar"
|
import Avatar from "../ui/avatar"
|
||||||
import { Badge } from "../ui/badge"
|
import { Badge } from "../ui/badge"
|
||||||
import { Input } from "../ui/input"
|
import { Input } from "../ui/input"
|
||||||
import { Progress } from "../ui/progress"
|
import { Progress } from "../ui/progress"
|
||||||
|
import { Textarea } from "../ui/textarea"
|
||||||
// #region Profile Page
|
// #region Profile Page
|
||||||
export default function ProfilePage({
|
export default function ProfilePage({
|
||||||
publicSandboxes,
|
publicSandboxes,
|
||||||
@ -75,6 +104,9 @@ export default function ProfilePage({
|
|||||||
generations={isOwnProfile ? loggedInUser.generations : undefined}
|
generations={isOwnProfile ? loggedInUser.generations : undefined}
|
||||||
isOwnProfile={isOwnProfile}
|
isOwnProfile={isOwnProfile}
|
||||||
tier={profileOwner.tier}
|
tier={profileOwner.tier}
|
||||||
|
bio={profileOwner.bio}
|
||||||
|
personalWebsite={profileOwner.personalWebsite}
|
||||||
|
socialLinks={profileOwner.links}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="md:col-span-2">
|
<div className="md:col-span-2">
|
||||||
@ -101,11 +133,17 @@ function ProfileCard({
|
|||||||
joinedDate,
|
joinedDate,
|
||||||
generations,
|
generations,
|
||||||
isOwnProfile,
|
isOwnProfile,
|
||||||
|
bio,
|
||||||
|
personalWebsite,
|
||||||
|
socialLinks = [],
|
||||||
tier,
|
tier,
|
||||||
}: {
|
}: {
|
||||||
name: string
|
name: string
|
||||||
username: string
|
username: string
|
||||||
avatarUrl: string | null
|
avatarUrl: string | null
|
||||||
|
bio: string | null
|
||||||
|
personalWebsite: string | null
|
||||||
|
socialLinks: UserLink[]
|
||||||
sandboxes: SandboxWithLiked[]
|
sandboxes: SandboxWithLiked[]
|
||||||
joinedDate: Date
|
joinedDate: Date
|
||||||
generations?: number
|
generations?: number
|
||||||
@ -113,9 +151,8 @@ function ProfileCard({
|
|||||||
tier: string
|
tier: string
|
||||||
}) {
|
}) {
|
||||||
const { user } = useUser()
|
const { user } = useUser()
|
||||||
const router = useRouter()
|
|
||||||
const [isEditing, setIsEditing] = useState(false)
|
const [isEditing, setIsEditing] = useState(false)
|
||||||
const [formState, formAction] = useFormState(updateUser, {})
|
|
||||||
const joinedAt = useMemo(() => {
|
const joinedAt = useMemo(() => {
|
||||||
const date = new Date(joinedDate).toLocaleDateString("en-US", {
|
const date = new Date(joinedDate).toLocaleDateString("en-US", {
|
||||||
month: "long",
|
month: "long",
|
||||||
@ -140,103 +177,126 @@ function ProfileCard({
|
|||||||
}
|
}
|
||||||
}, [sandboxes])
|
}, [sandboxes])
|
||||||
|
|
||||||
useEffect(() => {
|
const showAddMoreInfoBanner = useMemo(() => {
|
||||||
if ("message" in formState) {
|
return !bio && !personalWebsite && (socialLinks?.length ?? 0) === 0
|
||||||
toast.success(formState.message as String)
|
}, [personalWebsite, bio, socialLinks])
|
||||||
toggleEdit()
|
|
||||||
if ("newRoute" in formState && typeof formState.newRoute === "string") {
|
|
||||||
router.replace(formState.newRoute)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ("error" in formState) {
|
|
||||||
const error = formState.error
|
|
||||||
if (typeof error === "string") {
|
|
||||||
toast.error(error)
|
|
||||||
} else {
|
|
||||||
toast.error("An Error Occured")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [formState])
|
|
||||||
return (
|
return (
|
||||||
<Card className="mb-6 md:mb-0 sticky top-6">
|
<Card className="mb-6 md:mb-0 sticky top-6">
|
||||||
{isOwnProfile && (
|
{isOwnProfile && (
|
||||||
<Button
|
<div className="absolute top-2 right-2 flex flex-col gap-2">
|
||||||
onClick={toggleEdit}
|
<TooltipProvider>
|
||||||
aria-label={isEditing ? "close edit form" : "open edit form"}
|
<Tooltip>
|
||||||
size="smIcon"
|
<TooltipTrigger asChild>
|
||||||
variant="secondary"
|
<Button
|
||||||
className="rounded-full absolute top-2 right-2"
|
onClick={toggleEdit}
|
||||||
>
|
aria-label={isEditing ? "close edit form" : "open edit form"}
|
||||||
{isEditing ? <X className="size-4" /> : <Edit className="size-4" />}
|
size="smIcon"
|
||||||
</Button>
|
variant="secondary"
|
||||||
|
className="rounded-full relative"
|
||||||
|
>
|
||||||
|
{isEditing ? (
|
||||||
|
<X className="size-4" />
|
||||||
|
) : showAddMoreInfoBanner ? (
|
||||||
|
<>
|
||||||
|
<Sparkles className="size-4 text-yellow-400 z-[2]" />
|
||||||
|
<div className="z-[1] absolute inset-0 rounded-full bg-secondary animate-ping" />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Edit className="size-4" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>
|
||||||
|
{showAddMoreInfoBanner
|
||||||
|
? "Add more information to your profile"
|
||||||
|
: "Edit your profile"}
|
||||||
|
</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
<CardContent className="flex flex-col gap-4 items-center pt-6">
|
<CardContent className="flex flex-col gap-4 pt-6">
|
||||||
<Avatar name={name} avatarUrl={avatarUrl} className="size-36" />
|
{isEditing ? (
|
||||||
|
<div className="flex flex-col gap-2 items-center ">
|
||||||
{!isEditing ? (
|
<Avatar name={name} avatarUrl={avatarUrl} className="size-36" />
|
||||||
<div className="space-y-2">
|
<EditProfileForm
|
||||||
<CardTitle className="text-2xl text-center">{name}</CardTitle>
|
{...{
|
||||||
<CardDescription className="text-center">{`@${username}`}</CardDescription>
|
name,
|
||||||
|
username,
|
||||||
|
avatarUrl,
|
||||||
|
bio,
|
||||||
|
personalWebsite,
|
||||||
|
socialLinks,
|
||||||
|
toggleEdit,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<form action={formAction} className="flex flex-col gap-2">
|
|
||||||
<Input
|
|
||||||
name="id"
|
|
||||||
placeholder="ID"
|
|
||||||
className="hidden "
|
|
||||||
value={user?.id}
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
name="oldUsername"
|
|
||||||
placeholder="ID"
|
|
||||||
className="hidden "
|
|
||||||
value={user?.username ?? undefined}
|
|
||||||
/>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label htmlFor="input-name">Name</Label>
|
|
||||||
<Input
|
|
||||||
id="input-name"
|
|
||||||
name="name"
|
|
||||||
placeholder="Name"
|
|
||||||
defaultValue={name}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label htmlFor="input-username">User name</Label>
|
|
||||||
<div className="relative">
|
|
||||||
<Input
|
|
||||||
id="input-username"
|
|
||||||
className="peer ps-6"
|
|
||||||
type="text"
|
|
||||||
name="username"
|
|
||||||
placeholder="Username"
|
|
||||||
defaultValue={username}
|
|
||||||
/>
|
|
||||||
<span className="pointer-events-none absolute inset-y-0 start-0 flex items-center justify-center ps-2 text-sm text-muted-foreground peer-disabled:opacity-50">
|
|
||||||
@
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SubmitButton />
|
|
||||||
</form>
|
|
||||||
)}
|
|
||||||
{!isEditing && (
|
|
||||||
<>
|
<>
|
||||||
<div className="flex gap-6">
|
<div className="flex flex-col gap-2 items-center">
|
||||||
<StatsItem icon={Package2} label={stats.sandboxes} />
|
<Avatar name={name} avatarUrl={avatarUrl} className="size-36" />
|
||||||
<StatsItem icon={Heart} label={stats.likes} />
|
<div className="space-y-1">
|
||||||
</div>
|
<CardTitle className="text-2xl text-center">{name}</CardTitle>
|
||||||
<div className="flex flex-col items-center gap-2">
|
<CardDescription className="text-center">{`@${username}`}</CardDescription>
|
||||||
<p className="text-xs text-muted-foreground">{joinedAt}</p>
|
</div>
|
||||||
{typeof generations === "number" && (
|
{bio && <p className="text-sm text-center">{bio}</p>}
|
||||||
<SubscriptionBadge
|
{(socialLinks.length > 0 || personalWebsite) && (
|
||||||
generations={generations}
|
<div className="flex gap-2 justify-center">
|
||||||
tier={tier as keyof typeof TIERS}
|
{personalWebsite && (
|
||||||
/>
|
<Button variant="secondary" size="smIcon" asChild>
|
||||||
|
<a
|
||||||
|
href={personalWebsite}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<Globe className="size-4" />
|
||||||
|
<span className="sr-only">Personal Website</span>
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{socialLinks.map((link, index) => {
|
||||||
|
const Icon = socialIcons[link.platform]
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={index}
|
||||||
|
variant="secondary"
|
||||||
|
size="smIcon"
|
||||||
|
asChild
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href={link.url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<Icon className="size-4" />
|
||||||
|
<span className="sr-only">{link.platform}</span>
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex flex-col gap-1 items-center">
|
||||||
|
{typeof generations === "number" && (
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<SubscriptionBadge
|
||||||
|
generations={generations}
|
||||||
|
tier={tier as keyof typeof TIERS}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<StatsItem icon={Package2} label={stats.sandboxes} />
|
||||||
|
<StatsItem icon={Heart} label={stats.likes} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs mt-2 text-muted-foreground text-center">
|
||||||
|
{joinedAt}
|
||||||
|
</p>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@ -244,13 +304,213 @@ function ProfileCard({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function SubmitButton() {
|
function EditProfileForm(props: {
|
||||||
const { pending } = useFormStatus()
|
name: string
|
||||||
|
username: string
|
||||||
|
avatarUrl: string | null
|
||||||
|
bio: string | null
|
||||||
|
personalWebsite: string | null
|
||||||
|
socialLinks: UserLink[]
|
||||||
|
toggleEdit: () => void
|
||||||
|
}) {
|
||||||
|
const router = useRouter()
|
||||||
|
const { user } = useUser()
|
||||||
|
const formRef = useRef<HTMLFormElement>(null)
|
||||||
|
const [formState, formAction] = useFormState(updateUser, {
|
||||||
|
message: "",
|
||||||
|
})
|
||||||
|
const [isPending, startTransition] = useTransition()
|
||||||
|
const { name, username, bio, personalWebsite, socialLinks, toggleEdit } =
|
||||||
|
props
|
||||||
|
const form = useForm<EditUserSchema>({
|
||||||
|
resolver: zodResolver(editUserSchema),
|
||||||
|
defaultValues: {
|
||||||
|
oldUsername: username,
|
||||||
|
id: user?.id,
|
||||||
|
name,
|
||||||
|
username,
|
||||||
|
bio: bio ?? "",
|
||||||
|
personalWebsite: personalWebsite ?? "",
|
||||||
|
links:
|
||||||
|
socialLinks.length > 0
|
||||||
|
? socialLinks
|
||||||
|
: [{ url: "", platform: "generic" }],
|
||||||
|
...(formState.fields ?? {}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const { fields, append, remove } = useFieldArray({
|
||||||
|
name: "links",
|
||||||
|
control: form.control,
|
||||||
|
})
|
||||||
|
useEffect(() => {
|
||||||
|
const message = formState.message
|
||||||
|
if (!Boolean(message)) return
|
||||||
|
if ("error" in formState) {
|
||||||
|
toast.error(formState.message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
toast.success(formState.message as String)
|
||||||
|
toggleEdit()
|
||||||
|
if (formState?.newRoute) {
|
||||||
|
router.replace(formState.newRoute)
|
||||||
|
}
|
||||||
|
}, [formState])
|
||||||
return (
|
return (
|
||||||
<Button size="sm" type="submit" className="w-full mt-2" disabled={pending}>
|
<Form {...form}>
|
||||||
{pending && <Loader2 className="animate-spin mr-2 h-4 w-4" />}
|
<form
|
||||||
Save
|
ref={formRef}
|
||||||
|
action={formAction}
|
||||||
|
onSubmit={(evt) => {
|
||||||
|
evt.preventDefault()
|
||||||
|
form.handleSubmit(() => {
|
||||||
|
startTransition(() => {
|
||||||
|
formAction(new FormData(formRef.current!))
|
||||||
|
})
|
||||||
|
})(evt)
|
||||||
|
}}
|
||||||
|
className="space-y-3 w-full"
|
||||||
|
>
|
||||||
|
<input type="hidden" name="id" value={user?.id} />
|
||||||
|
<input type="hidden" name="oldUsername" value={username} />
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="name"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Name</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="marie doe" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="username"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>User name</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<div className="relative">
|
||||||
|
<Input
|
||||||
|
className="peer ps-6"
|
||||||
|
type="text"
|
||||||
|
placeholder="Username"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
<span className="pointer-events-none absolute inset-y-0 start-0 flex items-center justify-center ps-2 text-sm text-muted-foreground peer-disabled:opacity-50">
|
||||||
|
@
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="bio"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Bio</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea
|
||||||
|
placeholder="hi, I love building things!"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="personalWebsite"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Personal Website</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="https://chillguy.dev" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
{fields.map((field, index) => (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
key={field.id}
|
||||||
|
name={`links.${index}`}
|
||||||
|
render={({ field: { onChange, value, ...field } }) => {
|
||||||
|
const Icon = socialIcons[value.platform] ?? socialIcons.generic
|
||||||
|
return (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel className={cn(index !== 0 && "sr-only")}>
|
||||||
|
Social Links
|
||||||
|
</FormLabel>
|
||||||
|
<FormDescription className={cn(index !== 0 && "sr-only")}>
|
||||||
|
Add links to your blogs or social media profiles.
|
||||||
|
</FormDescription>
|
||||||
|
<FormControl>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<div className="relative flex-1">
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
className="peer ps-9"
|
||||||
|
value={value.url}
|
||||||
|
onChange={(e) =>
|
||||||
|
onChange(parseSocialLink(e.currentTarget.value))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<div className="pointer-events-none absolute inset-y-0 start-0 flex items-center justify-center ps-3 text-muted-foreground/80 peer-disabled:opacity-50">
|
||||||
|
<Icon
|
||||||
|
size={16}
|
||||||
|
strokeWidth={2}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
size="smIcon"
|
||||||
|
type="button"
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => remove(index)}
|
||||||
|
>
|
||||||
|
<Trash2 className="size-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="mt-2"
|
||||||
|
onClick={() => append({ url: "", platform: "generic" })}
|
||||||
|
>
|
||||||
|
Add URL
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<SubmitButton {...{ isPending }} />
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
function SubmitButton({ isPending }: { isPending: boolean }) {
|
||||||
|
const formStatus = useFormStatus()
|
||||||
|
const { pending } = formStatus
|
||||||
|
const pend = pending || isPending
|
||||||
|
return (
|
||||||
|
<Button size="sm" type="submit" className="w-full mt-2" disabled={pend}>
|
||||||
|
{pend && <Loader2 className="animate-spin mr-2 h-4 w-4" />}
|
||||||
|
Save Changes
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -441,8 +701,8 @@ interface StatsItemProps {
|
|||||||
|
|
||||||
const StatsItem = ({ icon: Icon, label }: StatsItemProps) => (
|
const StatsItem = ({ icon: Icon, label }: StatsItemProps) => (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Icon size={18} />
|
<Icon size={16} />
|
||||||
<span className="text-sm text-muted-foreground">{label}</span>
|
<span className="text-sm text-muted-foreground">{label}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
// #endregion
|
// #endregion
|
||||||
@ -462,8 +722,8 @@ const SubscriptionBadge = ({
|
|||||||
</Badge>
|
</Badge>
|
||||||
<HoverCard>
|
<HoverCard>
|
||||||
<HoverCardTrigger>
|
<HoverCardTrigger>
|
||||||
<Button variant="ghost" size="smIcon">
|
<Button variant="ghost" size="smIcon" className="size-[26px]">
|
||||||
<Info size={20} />
|
<Info size={16} />
|
||||||
</Button>
|
</Button>
|
||||||
</HoverCardTrigger>
|
</HoverCardTrigger>
|
||||||
<HoverCardContent>
|
<HoverCardContent>
|
||||||
|
@ -9,14 +9,19 @@ import { Button } from "../ui/button"
|
|||||||
export default function ProfileNavbar({ userData }: { userData: User }) {
|
export default function ProfileNavbar({ userData }: { userData: User }) {
|
||||||
return (
|
return (
|
||||||
<nav className=" py-2 px-4 w-full flex items-center justify-between border-b border-border">
|
<nav className=" py-2 px-4 w-full flex items-center justify-between border-b border-border">
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-2">
|
||||||
<Link
|
<Link
|
||||||
href="/"
|
href="/"
|
||||||
className="ring-offset-2 ring-offset-background focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none rounded-sm"
|
className="ring-offset-2 ring-offset-background focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none rounded-sm"
|
||||||
>
|
>
|
||||||
<Image src={Logo} alt="Logo" width={36} height={36} />
|
<Image src={Logo} alt="Logo" width={36} height={36} />
|
||||||
</Link>
|
</Link>
|
||||||
<div className="text-sm font-medium flex items-center">Sandbox</div>
|
<h1 className="text-xl">
|
||||||
|
<span className="font-semibold">Sandbox</span>{" "}
|
||||||
|
<span className="text-xs font-medium text-muted-foreground">
|
||||||
|
by gitwit
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<ThemeSwitcher />
|
<ThemeSwitcher />
|
||||||
|
@ -26,7 +26,7 @@ const buttonVariants = cva(
|
|||||||
sm: "h-8 rounded-md px-3 text-xs",
|
sm: "h-8 rounded-md px-3 text-xs",
|
||||||
lg: "h-10 rounded-md px-8",
|
lg: "h-10 rounded-md px-8",
|
||||||
icon: "h-9 w-9",
|
icon: "h-9 w-9",
|
||||||
smIcon: "h-8 w-8",
|
smIcon: "size-8",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||||
import { Slot } from "@radix-ui/react-slot"
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
import * as React from "react"
|
|
||||||
import {
|
import {
|
||||||
Controller,
|
Controller,
|
||||||
ControllerProps,
|
ControllerProps,
|
||||||
@ -10,8 +12,8 @@ import {
|
|||||||
useFormContext,
|
useFormContext,
|
||||||
} from "react-hook-form"
|
} from "react-hook-form"
|
||||||
|
|
||||||
import { Label } from "@/components/ui/label"
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
import { Label } from "@/components/ui/label"
|
||||||
|
|
||||||
const Form = FormProvider
|
const Form = FormProvider
|
||||||
|
|
||||||
@ -93,7 +95,7 @@ const FormLabel = React.forwardRef<
|
|||||||
return (
|
return (
|
||||||
<Label
|
<Label
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(className)}
|
className={cn(error && "text-destructive", className)}
|
||||||
htmlFor={formItemId}
|
htmlFor={formItemId}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
@ -165,12 +167,12 @@ const FormMessage = React.forwardRef<
|
|||||||
FormMessage.displayName = "FormMessage"
|
FormMessage.displayName = "FormMessage"
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
useFormField,
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
|
||||||
FormDescription,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
useFormField,
|
FormField,
|
||||||
}
|
}
|
||||||
|
48
frontend/components/ui/scroll-area.tsx
Normal file
48
frontend/components/ui/scroll-area.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const ScrollArea = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<ScrollAreaPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn("relative overflow-hidden", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
||||||
|
{children}
|
||||||
|
</ScrollAreaPrimitive.Viewport>
|
||||||
|
<ScrollBar />
|
||||||
|
<ScrollAreaPrimitive.Corner />
|
||||||
|
</ScrollAreaPrimitive.Root>
|
||||||
|
))
|
||||||
|
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
|
||||||
|
|
||||||
|
const ScrollBar = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||||
|
>(({ className, orientation = "vertical", ...props }, ref) => (
|
||||||
|
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||||
|
ref={ref}
|
||||||
|
orientation={orientation}
|
||||||
|
className={cn(
|
||||||
|
"flex touch-none select-none transition-colors",
|
||||||
|
orientation === "vertical" &&
|
||||||
|
"h-full w-2.5 border-l border-l-transparent p-[1px]",
|
||||||
|
orientation === "horizontal" &&
|
||||||
|
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
|
||||||
|
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||||
|
))
|
||||||
|
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
|
||||||
|
|
||||||
|
export { ScrollArea, ScrollBar }
|
22
frontend/components/ui/textarea.tsx
Normal file
22
frontend/components/ui/textarea.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Textarea = React.forwardRef<
|
||||||
|
HTMLTextAreaElement,
|
||||||
|
React.ComponentProps<"textarea">
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<textarea
|
||||||
|
className={cn(
|
||||||
|
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
Textarea.displayName = "Textarea"
|
||||||
|
|
||||||
|
export { Textarea }
|
@ -1,8 +1,11 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
import { ThemeProvider as NextThemesProvider } from "next-themes"
|
import { ThemeProvider as NextThemesProvider } from "next-themes"
|
||||||
import { type ThemeProviderProps } from "next-themes/dist/types"
|
|
||||||
|
|
||||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
export function ThemeProvider({
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof NextThemesProvider>) {
|
||||||
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
@ -29,4 +29,4 @@ const TooltipContent = React.forwardRef<
|
|||||||
))
|
))
|
||||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
||||||
|
|
||||||
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }
|
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
||||||
|
@ -20,7 +20,9 @@ interface TerminalContextType {
|
|||||||
createNewTerminal: (command?: string) => Promise<void>
|
createNewTerminal: (command?: string) => Promise<void>
|
||||||
closeTerminal: (id: string) => void
|
closeTerminal: (id: string) => void
|
||||||
deploy: (callback: () => void) => void
|
deploy: (callback: () => void) => void
|
||||||
getAppExists: ((appName: string) => Promise<boolean>) | null
|
getAppExists:
|
||||||
|
| ((appName: string) => Promise<{ success: boolean; exists?: boolean }>)
|
||||||
|
| null
|
||||||
}
|
}
|
||||||
|
|
||||||
const TerminalContext = createContext<TerminalContextType | undefined>(
|
const TerminalContext = createContext<TerminalContextType | undefined>(
|
||||||
@ -92,16 +94,18 @@ export const TerminalProvider: React.FC<{ children: React.ReactNode }> = ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAppExists = async (appName: string): Promise<boolean> => {
|
const getAppExists = async (
|
||||||
|
appName: string
|
||||||
|
): Promise<{ success: boolean; exists?: boolean }> => {
|
||||||
console.log("Is there a socket: " + !!socket)
|
console.log("Is there a socket: " + !!socket)
|
||||||
if (!socket) {
|
if (!socket) {
|
||||||
console.error("Couldn't check if app exists: No socket")
|
console.error("Couldn't check if app exists: No socket")
|
||||||
return false
|
return { success: false }
|
||||||
}
|
}
|
||||||
const response: { exists: boolean } = await new Promise((resolve) =>
|
const response: { success: boolean; exists?: boolean } = await new Promise(
|
||||||
socket.emit("getAppExists", { appName }, resolve)
|
(resolve) => socket.emit("getAppExists", { appName }, resolve)
|
||||||
)
|
)
|
||||||
return response.exists
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
const value = {
|
const value = {
|
||||||
|
@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
import { revalidatePath } from "next/cache"
|
import { revalidatePath } from "next/cache"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
import { editUserSchema } from "./schema"
|
||||||
|
import { UserLink } from "./types"
|
||||||
|
import { parseSocialLink } from "./utils"
|
||||||
|
|
||||||
export async function createSandbox(body: {
|
export async function createSandbox(body: {
|
||||||
type: string
|
type: string
|
||||||
@ -94,7 +97,7 @@ export async function unshareSandbox(sandboxId: string, userId: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function toggleLike(sandboxId: string, userId: string) {
|
export async function toggleLike(sandboxId: string, userId: string) {
|
||||||
await fetch(
|
const res = await fetch(
|
||||||
`${process.env.NEXT_PUBLIC_DATABASE_WORKER_URL}/api/sandbox/like`,
|
`${process.env.NEXT_PUBLIC_DATABASE_WORKER_URL}/api/sandbox/like`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@ -123,20 +126,31 @@ const UpdateErrorSchema = z.object({
|
|||||||
.optional(),
|
.optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export async function updateUser(prevState: any, formData: FormData) {
|
interface FormState {
|
||||||
const data = Object.fromEntries(formData)
|
message: string
|
||||||
|
error?: any
|
||||||
const schema = z.object({
|
newRoute?: string
|
||||||
id: z.string(),
|
fields?: Record<string, unknown>
|
||||||
username: z.string(),
|
}
|
||||||
oldUsername: z.string(),
|
export async function updateUser(
|
||||||
name: z.string(),
|
prevState: any,
|
||||||
|
formData: FormData
|
||||||
|
): Promise<FormState> {
|
||||||
|
let data = Object.fromEntries(formData)
|
||||||
|
let links: UserLink[] = []
|
||||||
|
Object.entries(data).forEach(([key, value]) => {
|
||||||
|
if (key.startsWith("link")) {
|
||||||
|
const [_, index] = key.split(".")
|
||||||
|
if (value) {
|
||||||
|
links.splice(parseInt(index), 0, parseSocialLink(value as string))
|
||||||
|
delete data[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
console.log(data)
|
// @ts-ignore
|
||||||
|
data.links = links
|
||||||
try {
|
try {
|
||||||
const validatedData = schema.parse(data)
|
const validatedData = editUserSchema.parse(data)
|
||||||
|
|
||||||
const changedUsername = validatedData.username !== validatedData.oldUsername
|
const changedUsername = validatedData.username !== validatedData.oldUsername
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`${process.env.NEXT_PUBLIC_DATABASE_WORKER_URL}/api/user`,
|
`${process.env.NEXT_PUBLIC_DATABASE_WORKER_URL}/api/user`,
|
||||||
@ -150,6 +164,9 @@ export async function updateUser(prevState: any, formData: FormData) {
|
|||||||
id: validatedData.id,
|
id: validatedData.id,
|
||||||
username: data.username ?? undefined,
|
username: data.username ?? undefined,
|
||||||
name: data.name ?? undefined,
|
name: data.name ?? undefined,
|
||||||
|
bio: data.bio ?? undefined,
|
||||||
|
personalWebsite: data.personalWebsite ?? undefined,
|
||||||
|
links: data.links ?? undefined,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -160,11 +177,11 @@ export async function updateUser(prevState: any, formData: FormData) {
|
|||||||
const parseResult = UpdateErrorSchema.safeParse(responseData)
|
const parseResult = UpdateErrorSchema.safeParse(responseData)
|
||||||
|
|
||||||
if (!parseResult.success) {
|
if (!parseResult.success) {
|
||||||
return { error: "Unexpected error occurred" }
|
return {
|
||||||
}
|
message: "Unexpected error occurred",
|
||||||
|
error: parseResult.error,
|
||||||
if (parseResult.data.error) {
|
fields: validatedData,
|
||||||
return parseResult.data
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changedUsername) {
|
if (changedUsername) {
|
||||||
@ -175,12 +192,13 @@ export async function updateUser(prevState: any, formData: FormData) {
|
|||||||
return { message: "Successfully updated" }
|
return { message: "Successfully updated" }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof z.ZodError) {
|
if (error instanceof z.ZodError) {
|
||||||
console.log(error)
|
|
||||||
return {
|
return {
|
||||||
error: error.errors?.[0].message,
|
message: "Invalid data",
|
||||||
|
error: error.errors,
|
||||||
|
fields: data,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { error: "An unexpected error occurred" }
|
return { message: "An unexpected error occurred", fields: data }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
14
frontend/lib/constants/index.ts
Normal file
14
frontend/lib/constants/index.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
export const KNOWN_PLATFORMS = [
|
||||||
|
"github",
|
||||||
|
"twitter",
|
||||||
|
"instagram",
|
||||||
|
"bluesky",
|
||||||
|
"linkedin",
|
||||||
|
"youtube",
|
||||||
|
"twitch",
|
||||||
|
"discord",
|
||||||
|
"mastodon",
|
||||||
|
"threads",
|
||||||
|
"gitlab",
|
||||||
|
"generic",
|
||||||
|
] as const
|
@ -1,3 +1,37 @@
|
|||||||
|
import {
|
||||||
|
AtSign,
|
||||||
|
Github,
|
||||||
|
GitlabIcon as GitlabLogo,
|
||||||
|
Globe,
|
||||||
|
Instagram,
|
||||||
|
Link,
|
||||||
|
Linkedin,
|
||||||
|
MessageCircle,
|
||||||
|
Twitch,
|
||||||
|
Twitter,
|
||||||
|
Youtube,
|
||||||
|
} from "lucide-react"
|
||||||
|
import { KnownPlatform } from "../types"
|
||||||
|
|
||||||
|
export const socialIcons: Record<
|
||||||
|
KnownPlatform | "website",
|
||||||
|
React.ComponentType<any>
|
||||||
|
> = {
|
||||||
|
github: Github,
|
||||||
|
twitter: Twitter,
|
||||||
|
instagram: Instagram,
|
||||||
|
bluesky: AtSign,
|
||||||
|
linkedin: Linkedin,
|
||||||
|
youtube: Youtube,
|
||||||
|
twitch: Twitch,
|
||||||
|
discord: MessageCircle,
|
||||||
|
mastodon: AtSign,
|
||||||
|
threads: AtSign,
|
||||||
|
gitlab: GitlabLogo,
|
||||||
|
generic: Link,
|
||||||
|
website: Globe,
|
||||||
|
}
|
||||||
|
|
||||||
export const projectTemplates: {
|
export const projectTemplates: {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
|
20
frontend/lib/schema/index.ts
Normal file
20
frontend/lib/schema/index.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { z } from "zod"
|
||||||
|
import { KNOWN_PLATFORMS } from "../constants"
|
||||||
|
|
||||||
|
export const editUserSchema = z.object({
|
||||||
|
id: z.string().trim(),
|
||||||
|
username: z.string().trim().min(1, "Username must be at least 1 character"),
|
||||||
|
oldUsername: z.string().trim(),
|
||||||
|
name: z.string().trim().min(1, "Name must be at least 1 character"),
|
||||||
|
bio: z.string().trim().optional(),
|
||||||
|
personalWebsite: z.string().trim().optional(),
|
||||||
|
links: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
url: z.string().trim(),
|
||||||
|
platform: z.enum(KNOWN_PLATFORMS),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.catch([]),
|
||||||
|
})
|
||||||
|
export type EditUserSchema = z.infer<typeof editUserSchema>
|
@ -1,5 +1,7 @@
|
|||||||
// DB Types
|
// DB Types
|
||||||
|
|
||||||
|
import { KNOWN_PLATFORMS } from "./constants"
|
||||||
|
|
||||||
export type User = {
|
export type User = {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
@ -8,11 +10,20 @@ export type User = {
|
|||||||
avatarUrl: string | null
|
avatarUrl: string | null
|
||||||
createdAt: Date
|
createdAt: Date
|
||||||
generations: number
|
generations: number
|
||||||
sandbox: Sandbox[]
|
bio: string | null
|
||||||
usersToSandboxes: UsersToSandboxes[]
|
personalWebsite: string | null
|
||||||
|
links: UserLink[]
|
||||||
tier: "FREE" | "PRO" | "ENTERPRISE"
|
tier: "FREE" | "PRO" | "ENTERPRISE"
|
||||||
tierExpiresAt: Date
|
tierExpiresAt: Date
|
||||||
lastResetDate?: number
|
lastResetDate: number
|
||||||
|
sandbox: Sandbox[]
|
||||||
|
usersToSandboxes: UsersToSandboxes[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type KnownPlatform = (typeof KNOWN_PLATFORMS)[number]
|
||||||
|
export type UserLink = {
|
||||||
|
url: string
|
||||||
|
platform: KnownPlatform
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Sandbox = {
|
export type Sandbox = {
|
||||||
|
@ -2,7 +2,7 @@ import { type ClassValue, clsx } from "clsx"
|
|||||||
// import { toast } from "sonner"
|
// import { toast } from "sonner"
|
||||||
import { twMerge } from "tailwind-merge"
|
import { twMerge } from "tailwind-merge"
|
||||||
import fileExtToLang from "./file-extension-to-language.json"
|
import fileExtToLang from "./file-extension-to-language.json"
|
||||||
import { TFile, TFolder } from "./types"
|
import { KnownPlatform, TFile, TFolder, UserLink } from "./types"
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs))
|
return twMerge(clsx(inputs))
|
||||||
@ -98,3 +98,57 @@ export function sortFileExplorer(
|
|||||||
return item
|
return item
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parseSocialLink(url: string): UserLink {
|
||||||
|
try {
|
||||||
|
// Handle empty or invalid URLs
|
||||||
|
if (!url) return { url: "", platform: "generic" }
|
||||||
|
|
||||||
|
// Remove protocol and www prefix for consistent parsing
|
||||||
|
const cleanUrl = url
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/^https?:\/\//, "")
|
||||||
|
.replace(/^www\./, "")
|
||||||
|
.split("/")[0] // Get just the domain part
|
||||||
|
|
||||||
|
// Platform detection mapping
|
||||||
|
const platformPatterns: Record<
|
||||||
|
Exclude<KnownPlatform, "generic">,
|
||||||
|
RegExp
|
||||||
|
> = {
|
||||||
|
github: /github\.com/,
|
||||||
|
twitter: /(?:twitter\.com|x\.com|t\.co)/,
|
||||||
|
instagram: /instagram\.com/,
|
||||||
|
bluesky: /(?:bsky\.app|bluesky\.social)/,
|
||||||
|
linkedin: /linkedin\.com/,
|
||||||
|
youtube: /(?:youtube\.com|youtu\.be)/,
|
||||||
|
twitch: /twitch\.tv/,
|
||||||
|
discord: /discord\.(?:gg|com)/,
|
||||||
|
mastodon: /mastodon\.(?:social|online|world)/,
|
||||||
|
threads: /threads\.net/,
|
||||||
|
gitlab: /gitlab\.com/,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check URL against each pattern
|
||||||
|
for (const [platform, pattern] of Object.entries(platformPatterns)) {
|
||||||
|
if (pattern.test(cleanUrl)) {
|
||||||
|
return {
|
||||||
|
url,
|
||||||
|
platform: platform as KnownPlatform,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to generic if no match found
|
||||||
|
return {
|
||||||
|
url,
|
||||||
|
platform: "generic",
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error parsing social link:", error)
|
||||||
|
return {
|
||||||
|
url: url || "",
|
||||||
|
platform: "generic",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
512
frontend/package-lock.json
generated
512
frontend/package-lock.json
generated
@ -13,7 +13,7 @@
|
|||||||
"@clerk/nextjs": "^4.29.12",
|
"@clerk/nextjs": "^4.29.12",
|
||||||
"@clerk/themes": "^1.7.12",
|
"@clerk/themes": "^1.7.12",
|
||||||
"@codemirror/lang-javascript": "^6.2.2",
|
"@codemirror/lang-javascript": "^6.2.2",
|
||||||
"@hookform/resolvers": "^3.3.4",
|
"@hookform/resolvers": "^3.9.1",
|
||||||
"@liveblocks/client": "^1.12.0",
|
"@liveblocks/client": "^1.12.0",
|
||||||
"@liveblocks/node": "^1.12.0",
|
"@liveblocks/node": "^1.12.0",
|
||||||
"@liveblocks/react": "^1.12.0",
|
"@liveblocks/react": "^1.12.0",
|
||||||
@ -27,14 +27,15 @@
|
|||||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||||
"@radix-ui/react-hover-card": "^1.1.2",
|
"@radix-ui/react-hover-card": "^1.1.2",
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
"@radix-ui/react-label": "^2.0.2",
|
"@radix-ui/react-label": "^2.1.1",
|
||||||
"@radix-ui/react-popover": "^1.1.1",
|
"@radix-ui/react-popover": "^1.1.1",
|
||||||
"@radix-ui/react-progress": "^1.1.0",
|
"@radix-ui/react-progress": "^1.1.0",
|
||||||
|
"@radix-ui/react-scroll-area": "^1.2.2",
|
||||||
"@radix-ui/react-select": "^2.0.0",
|
"@radix-ui/react-select": "^2.0.0",
|
||||||
"@radix-ui/react-slot": "^1.0.2",
|
"@radix-ui/react-slot": "^1.1.1",
|
||||||
"@radix-ui/react-switch": "^1.0.3",
|
"@radix-ui/react-switch": "^1.0.3",
|
||||||
"@radix-ui/react-tabs": "^1.1.1",
|
"@radix-ui/react-tabs": "^1.1.1",
|
||||||
"@radix-ui/react-tooltip": "^1.1.3",
|
"@radix-ui/react-tooltip": "^1.1.6",
|
||||||
"@react-three/fiber": "^8.16.6",
|
"@react-three/fiber": "^8.16.6",
|
||||||
"@uiw/codemirror-theme-vscode": "^4.23.5",
|
"@uiw/codemirror-theme-vscode": "^4.23.5",
|
||||||
"@uiw/react-codemirror": "^4.23.5",
|
"@uiw/react-codemirror": "^4.23.5",
|
||||||
@ -53,12 +54,12 @@
|
|||||||
"lucide-react": "^0.365.0",
|
"lucide-react": "^0.365.0",
|
||||||
"monaco-themes": "^0.4.4",
|
"monaco-themes": "^0.4.4",
|
||||||
"next": "14.1.3",
|
"next": "14.1.3",
|
||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.4.4",
|
||||||
"openai": "^4.73.1",
|
"openai": "^4.73.1",
|
||||||
"posthog-js": "^1.147.0",
|
"posthog-js": "^1.147.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"react-hook-form": "^7.51.3",
|
"react-hook-form": "^7.54.2",
|
||||||
"react-markdown": "^9.0.1",
|
"react-markdown": "^9.0.1",
|
||||||
"react-resizable-panels": "^2.0.16",
|
"react-resizable-panels": "^2.0.16",
|
||||||
"react-syntax-highlighter": "^15.5.0",
|
"react-syntax-highlighter": "^15.5.0",
|
||||||
@ -73,7 +74,7 @@
|
|||||||
"y-monaco": "^0.1.5",
|
"y-monaco": "^0.1.5",
|
||||||
"y-protocols": "^1.0.6",
|
"y-protocols": "^1.0.6",
|
||||||
"yjs": "^13.6.15",
|
"yjs": "^13.6.15",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.24.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/estree": "^1.0.6",
|
"@types/estree": "^1.0.6",
|
||||||
@ -909,9 +910,9 @@
|
|||||||
"integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q=="
|
"integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q=="
|
||||||
},
|
},
|
||||||
"node_modules/@hookform/resolvers": {
|
"node_modules/@hookform/resolvers": {
|
||||||
"version": "3.3.4",
|
"version": "3.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.9.1.tgz",
|
||||||
"integrity": "sha512-o5cgpGOuJYrd+iMKvkttOclgwRW86EsWJZZRC23prf0uU2i48Htq4PuT73AVb9ionFyZrwYEITuOFGF+BydEtQ==",
|
"integrity": "sha512-ud2HqmGBM0P0IABqoskKWI6PEf6ZDDBZkFqe2Vnl+mTHCEHzr3ISjjZyCwTjC/qpL25JC9aIDkloQejvMeq0ug==",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react-hook-form": "^7.0.0"
|
"react-hook-form": "^7.0.0"
|
||||||
}
|
}
|
||||||
@ -1365,6 +1366,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-slot": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-compose-refs": "1.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-arrow": {
|
"node_modules/@radix-ui/react-arrow": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz",
|
||||||
@ -1534,6 +1553,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-compose-refs": "1.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-compose-refs": {
|
"node_modules/@radix-ui/react-compose-refs": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz",
|
||||||
@ -1632,6 +1669,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-compose-refs": "1.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-direction": {
|
"node_modules/@radix-ui/react-direction": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.1.tgz",
|
||||||
@ -2116,18 +2171,39 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@radix-ui/react-label": {
|
"node_modules/@radix-ui/react-label": {
|
||||||
"version": "2.0.2",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.1.tgz",
|
||||||
"integrity": "sha512-N5ehvlM7qoTLx7nWPodsPYPgMzA5WM8zZChQg8nyFJKnDO5WHdba1vv5/H6IO5LtJMfD2Q3wh1qHFGNtK0w3bQ==",
|
"integrity": "sha512-UUw5E4e/2+4kFMH7+YxORXGWggtY6sM8WIwh5RZchhLuUg2H1hc98Py+pr8HMz6rdaYrK2t296ZEjYLOCO5uUw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.13.10",
|
"@radix-ui/react-primitive": "2.0.1"
|
||||||
"@radix-ui/react-primitive": "1.0.3"
|
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "*",
|
"@types/react": "*",
|
||||||
"@types/react-dom": "*",
|
"@types/react-dom": "*",
|
||||||
"react": "^16.8 || ^17.0 || ^18.0",
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-slot": "1.1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"@types/react": {
|
"@types/react": {
|
||||||
@ -2178,6 +2254,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-compose-refs": "1.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-popover": {
|
"node_modules/@radix-ui/react-popover": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.1.tgz",
|
||||||
@ -2693,6 +2787,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-compose-refs": "1.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-progress": {
|
"node_modules/@radix-ui/react-progress": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.0.tgz",
|
||||||
@ -2818,6 +2930,161 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-scroll-area": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-EFI1N/S3YxZEW/lJ/H1jY3njlvTd8tBmgKEn4GHi51+aMm94i6NmAJstsm5cu3yJwYqYc93gpCPm21FeAbFk6g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/number": "1.1.0",
|
||||||
|
"@radix-ui/primitive": "1.1.1",
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.1",
|
||||||
|
"@radix-ui/react-context": "1.1.1",
|
||||||
|
"@radix-ui/react-direction": "1.1.0",
|
||||||
|
"@radix-ui/react-presence": "1.1.2",
|
||||||
|
"@radix-ui/react-primitive": "2.0.1",
|
||||||
|
"@radix-ui/react-use-callback-ref": "1.1.0",
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/number": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ=="
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/primitive": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA=="
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-compose-refs": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-context": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-direction": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-presence": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.1",
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-primitive": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-slot": "1.1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-use-callback-ref": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-use-layout-effect": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-select": {
|
"node_modules/@radix-ui/react-select": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.0.0.tgz",
|
||||||
@ -2861,7 +3128,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@radix-ui/react-slot": {
|
"node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
|
||||||
"integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==",
|
"integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==",
|
||||||
@ -2879,6 +3146,37 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-slot": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-slot/node_modules/@radix-ui/react-compose-refs": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-switch": {
|
"node_modules/@radix-ui/react-switch": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.0.3.tgz",
|
||||||
@ -3192,23 +3490,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@radix-ui/react-tooltip": {
|
"node_modules/@radix-ui/react-tooltip": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.6.tgz",
|
||||||
"integrity": "sha512-Z4w1FIS0BqVFI2c1jZvb/uDVJijJjJ2ZMuPV81oVgTZ7g3BZxobplnMVvXtFWgtozdvYJ+MFWtwkM5S2HnAong==",
|
"integrity": "sha512-TLB5D8QLExS1uDn7+wH/bjEmRurNMTzNrtq7IjaS4kjion9NtzsTGkvR5+i7yc9q01Pi2KMM2cN3f8UG4IvvXA==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/primitive": "1.1.0",
|
"@radix-ui/primitive": "1.1.1",
|
||||||
"@radix-ui/react-compose-refs": "1.1.0",
|
"@radix-ui/react-compose-refs": "1.1.1",
|
||||||
"@radix-ui/react-context": "1.1.1",
|
"@radix-ui/react-context": "1.1.1",
|
||||||
"@radix-ui/react-dismissable-layer": "1.1.1",
|
"@radix-ui/react-dismissable-layer": "1.1.3",
|
||||||
"@radix-ui/react-id": "1.1.0",
|
"@radix-ui/react-id": "1.1.0",
|
||||||
"@radix-ui/react-popper": "1.2.0",
|
"@radix-ui/react-popper": "1.2.1",
|
||||||
"@radix-ui/react-portal": "1.1.2",
|
"@radix-ui/react-portal": "1.1.3",
|
||||||
"@radix-ui/react-presence": "1.1.1",
|
"@radix-ui/react-presence": "1.1.2",
|
||||||
"@radix-ui/react-primitive": "2.0.0",
|
"@radix-ui/react-primitive": "2.0.1",
|
||||||
"@radix-ui/react-slot": "1.1.0",
|
"@radix-ui/react-slot": "1.1.1",
|
||||||
"@radix-ui/react-use-controllable-state": "1.1.0",
|
"@radix-ui/react-use-controllable-state": "1.1.0",
|
||||||
"@radix-ui/react-visually-hidden": "1.1.0"
|
"@radix-ui/react-visually-hidden": "1.1.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "*",
|
"@types/react": "*",
|
||||||
@ -3226,18 +3523,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/primitive": {
|
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/primitive": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz",
|
||||||
"integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==",
|
"integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA=="
|
||||||
"license": "MIT"
|
|
||||||
},
|
},
|
||||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-arrow": {
|
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-arrow": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.1.tgz",
|
||||||
"integrity": "sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==",
|
"integrity": "sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-primitive": "2.0.0"
|
"@radix-ui/react-primitive": "2.0.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "*",
|
"@types/react": "*",
|
||||||
@ -3255,10 +3550,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-compose-refs": {
|
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-compose-refs": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz",
|
||||||
"integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==",
|
"integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==",
|
||||||
"license": "MIT",
|
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "*",
|
"@types/react": "*",
|
||||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
@ -3285,14 +3579,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-dismissable-layer": {
|
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-dismissable-layer": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.3.tgz",
|
||||||
"integrity": "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==",
|
"integrity": "sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/primitive": "1.1.0",
|
"@radix-ui/primitive": "1.1.1",
|
||||||
"@radix-ui/react-compose-refs": "1.1.0",
|
"@radix-ui/react-compose-refs": "1.1.1",
|
||||||
"@radix-ui/react-primitive": "2.0.0",
|
"@radix-ui/react-primitive": "2.0.1",
|
||||||
"@radix-ui/react-use-callback-ref": "1.1.0",
|
"@radix-ui/react-use-callback-ref": "1.1.0",
|
||||||
"@radix-ui/react-use-escape-keydown": "1.1.0"
|
"@radix-ui/react-use-escape-keydown": "1.1.0"
|
||||||
},
|
},
|
||||||
@ -3330,16 +3623,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-popper": {
|
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-popper": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.1.tgz",
|
||||||
"integrity": "sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==",
|
"integrity": "sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@floating-ui/react-dom": "^2.0.0",
|
"@floating-ui/react-dom": "^2.0.0",
|
||||||
"@radix-ui/react-arrow": "1.1.0",
|
"@radix-ui/react-arrow": "1.1.1",
|
||||||
"@radix-ui/react-compose-refs": "1.1.0",
|
"@radix-ui/react-compose-refs": "1.1.1",
|
||||||
"@radix-ui/react-context": "1.1.0",
|
"@radix-ui/react-context": "1.1.1",
|
||||||
"@radix-ui/react-primitive": "2.0.0",
|
"@radix-ui/react-primitive": "2.0.1",
|
||||||
"@radix-ui/react-use-callback-ref": "1.1.0",
|
"@radix-ui/react-use-callback-ref": "1.1.0",
|
||||||
"@radix-ui/react-use-layout-effect": "1.1.0",
|
"@radix-ui/react-use-layout-effect": "1.1.0",
|
||||||
"@radix-ui/react-use-rect": "1.1.0",
|
"@radix-ui/react-use-rect": "1.1.0",
|
||||||
@ -3361,28 +3653,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-context": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "*",
|
|
||||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-portal": {
|
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-portal": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.3.tgz",
|
||||||
"integrity": "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==",
|
"integrity": "sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-primitive": "2.0.0",
|
"@radix-ui/react-primitive": "2.0.1",
|
||||||
"@radix-ui/react-use-layout-effect": "1.1.0"
|
"@radix-ui/react-use-layout-effect": "1.1.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
@ -3401,12 +3677,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-presence": {
|
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-presence": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz",
|
||||||
"integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==",
|
"integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-compose-refs": "1.1.0",
|
"@radix-ui/react-compose-refs": "1.1.1",
|
||||||
"@radix-ui/react-use-layout-effect": "1.1.0"
|
"@radix-ui/react-use-layout-effect": "1.1.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
@ -3425,12 +3700,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-primitive": {
|
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-primitive": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz",
|
||||||
"integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==",
|
"integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-slot": "1.1.0"
|
"@radix-ui/react-slot": "1.1.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "*",
|
"@types/react": "*",
|
||||||
@ -3447,24 +3721,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@radix-ui/react-compose-refs": "1.1.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "*",
|
|
||||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-use-callback-ref": {
|
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-use-callback-ref": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
|
||||||
@ -3502,7 +3758,6 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz",
|
||||||
"integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==",
|
"integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-use-callback-ref": "1.1.0"
|
"@radix-ui/react-use-callback-ref": "1.1.0"
|
||||||
},
|
},
|
||||||
@ -3535,7 +3790,6 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz",
|
||||||
"integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==",
|
"integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/rect": "1.1.0"
|
"@radix-ui/rect": "1.1.0"
|
||||||
},
|
},
|
||||||
@ -3553,7 +3807,6 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz",
|
||||||
"integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==",
|
"integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-use-layout-effect": "1.1.0"
|
"@radix-ui/react-use-layout-effect": "1.1.0"
|
||||||
},
|
},
|
||||||
@ -3568,12 +3821,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-visually-hidden": {
|
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-visually-hidden": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.1.tgz",
|
||||||
"integrity": "sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ==",
|
"integrity": "sha512-vVfA2IZ9q/J+gEamvj761Oq1FpWgCDaNOOIfbPVp2MVPLEomUr5+Vf7kJGwQ24YxZSlQVar7Bes8kyTo5Dshpg==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-primitive": "2.0.0"
|
"@radix-ui/react-primitive": "2.0.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "*",
|
"@types/react": "*",
|
||||||
@ -3593,8 +3845,7 @@
|
|||||||
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/rect": {
|
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/rect": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz",
|
||||||
"integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==",
|
"integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg=="
|
||||||
"license": "MIT"
|
|
||||||
},
|
},
|
||||||
"node_modules/@radix-ui/react-use-callback-ref": {
|
"node_modules/@radix-ui/react-use-callback-ref": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
@ -7325,12 +7576,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/next-themes": {
|
"node_modules/next-themes": {
|
||||||
"version": "0.3.0",
|
"version": "0.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.4.tgz",
|
||||||
"integrity": "sha512-/QHIrsYpd6Kfk7xakK4svpDI5mmXP0gfvCoJdGpZQ2TOrQZmsW0QxjaiLn8wbIKjtm4BTSqLoix4lxYYOnLJ/w==",
|
"integrity": "sha512-LDQ2qIOJF0VnuVrrMSMLrWGjRMkq+0mpgl6e0juCLqdJ+oo8Q84JRWT6Wh11VDQKkMMe+dVzDKLWs5n87T+PkQ==",
|
||||||
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^16.8 || ^17 || ^18",
|
"react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
|
||||||
"react-dom": "^16.8 || ^17 || ^18"
|
"react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/next/node_modules/postcss": {
|
"node_modules/next/node_modules/postcss": {
|
||||||
@ -8028,18 +8280,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-hook-form": {
|
"node_modules/react-hook-form": {
|
||||||
"version": "7.51.3",
|
"version": "7.54.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.51.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.54.2.tgz",
|
||||||
"integrity": "sha512-cvJ/wbHdhYx8aviSWh28w9ImjmVsb5Y05n1+FW786vEZQJV5STNM0pW6ujS+oiBecb0ARBxJFyAnXj9+GHXACQ==",
|
"integrity": "sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.22.0"
|
"node": ">=18.0.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
"url": "https://opencollective.com/react-hook-form"
|
"url": "https://opencollective.com/react-hook-form"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^16.8.0 || ^17 || ^18"
|
"react": "^16.8.0 || ^17 || ^18 || ^19"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-markdown": {
|
"node_modules/react-markdown": {
|
||||||
@ -9724,9 +9976,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/zod": {
|
"node_modules/zod": {
|
||||||
"version": "3.23.8",
|
"version": "3.24.1",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz",
|
||||||
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
|
"integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
"@clerk/nextjs": "^4.29.12",
|
"@clerk/nextjs": "^4.29.12",
|
||||||
"@clerk/themes": "^1.7.12",
|
"@clerk/themes": "^1.7.12",
|
||||||
"@codemirror/lang-javascript": "^6.2.2",
|
"@codemirror/lang-javascript": "^6.2.2",
|
||||||
"@hookform/resolvers": "^3.3.4",
|
"@hookform/resolvers": "^3.9.1",
|
||||||
"@liveblocks/client": "^1.12.0",
|
"@liveblocks/client": "^1.12.0",
|
||||||
"@liveblocks/node": "^1.12.0",
|
"@liveblocks/node": "^1.12.0",
|
||||||
"@liveblocks/react": "^1.12.0",
|
"@liveblocks/react": "^1.12.0",
|
||||||
@ -28,14 +28,15 @@
|
|||||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||||
"@radix-ui/react-hover-card": "^1.1.2",
|
"@radix-ui/react-hover-card": "^1.1.2",
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
"@radix-ui/react-label": "^2.0.2",
|
"@radix-ui/react-label": "^2.1.1",
|
||||||
"@radix-ui/react-popover": "^1.1.1",
|
"@radix-ui/react-popover": "^1.1.1",
|
||||||
"@radix-ui/react-progress": "^1.1.0",
|
"@radix-ui/react-progress": "^1.1.0",
|
||||||
|
"@radix-ui/react-scroll-area": "^1.2.2",
|
||||||
"@radix-ui/react-select": "^2.0.0",
|
"@radix-ui/react-select": "^2.0.0",
|
||||||
"@radix-ui/react-slot": "^1.0.2",
|
"@radix-ui/react-slot": "^1.1.1",
|
||||||
"@radix-ui/react-switch": "^1.0.3",
|
"@radix-ui/react-switch": "^1.0.3",
|
||||||
"@radix-ui/react-tabs": "^1.1.1",
|
"@radix-ui/react-tabs": "^1.1.1",
|
||||||
"@radix-ui/react-tooltip": "^1.1.3",
|
"@radix-ui/react-tooltip": "^1.1.6",
|
||||||
"@react-three/fiber": "^8.16.6",
|
"@react-three/fiber": "^8.16.6",
|
||||||
"@uiw/codemirror-theme-vscode": "^4.23.5",
|
"@uiw/codemirror-theme-vscode": "^4.23.5",
|
||||||
"@uiw/react-codemirror": "^4.23.5",
|
"@uiw/react-codemirror": "^4.23.5",
|
||||||
@ -54,12 +55,12 @@
|
|||||||
"lucide-react": "^0.365.0",
|
"lucide-react": "^0.365.0",
|
||||||
"monaco-themes": "^0.4.4",
|
"monaco-themes": "^0.4.4",
|
||||||
"next": "14.1.3",
|
"next": "14.1.3",
|
||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.4.4",
|
||||||
"openai": "^4.73.1",
|
"openai": "^4.73.1",
|
||||||
"posthog-js": "^1.147.0",
|
"posthog-js": "^1.147.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"react-hook-form": "^7.51.3",
|
"react-hook-form": "^7.54.2",
|
||||||
"react-markdown": "^9.0.1",
|
"react-markdown": "^9.0.1",
|
||||||
"react-resizable-panels": "^2.0.16",
|
"react-resizable-panels": "^2.0.16",
|
||||||
"react-syntax-highlighter": "^15.5.0",
|
"react-syntax-highlighter": "^15.5.0",
|
||||||
@ -74,7 +75,7 @@
|
|||||||
"y-monaco": "^0.1.5",
|
"y-monaco": "^0.1.5",
|
||||||
"y-protocols": "^1.0.6",
|
"y-protocols": "^1.0.6",
|
||||||
"yjs": "^13.6.15",
|
"yjs": "^13.6.15",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.24.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/estree": "^1.0.6",
|
"@types/estree": "^1.0.6",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user