Compare commits
6 Commits
main
...
new-schema
Author | SHA1 | Date | |
---|---|---|---|
|
0828209455 | ||
|
94ca5b2c9f | ||
|
2a58d0a5e3 | ||
|
30c9da559f | ||
|
2262adca74 | ||
|
b486d22111 |
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"version": "5",
|
"version": "5",
|
||||||
"dialect": "sqlite",
|
"dialect": "sqlite",
|
||||||
"id": "6570ba20-a672-400c-8147-7ba533784918",
|
"id": "afe10bff-362b-402c-bdb5-038341692f35",
|
||||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||||
"tables": {
|
"tables": {
|
||||||
"sandbox": {
|
"sandbox": {
|
||||||
@ -35,12 +35,36 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP"
|
||||||
|
},
|
||||||
"user_id": {
|
"user_id": {
|
||||||
"name": "user_id",
|
"name": "user_id",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"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": {
|
"indexes": {
|
||||||
@ -93,6 +117,43 @@
|
|||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"name": "username",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"avatarUrl": {
|
||||||
|
"name": "avatarUrl",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP"
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"name": "image",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"generations": {
|
||||||
|
"name": "generations",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {
|
"indexes": {
|
||||||
@ -102,6 +163,13 @@
|
|||||||
"id"
|
"id"
|
||||||
],
|
],
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
|
},
|
||||||
|
"user_username_unique": {
|
||||||
|
"name": "user_username_unique",
|
||||||
|
"columns": [
|
||||||
|
"username"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"foreignKeys": {},
|
"foreignKeys": {},
|
||||||
@ -124,6 +192,13 @@
|
|||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"sharedOn": {
|
||||||
|
"name": "sharedOn",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {},
|
"indexes": {},
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"version": "5",
|
"version": "5",
|
||||||
"dialect": "sqlite",
|
"dialect": "sqlite",
|
||||||
"id": "9f64104a-4954-40c0-8155-17755ea0a243",
|
"id": "e570d5ac-700d-4e62-8a46-482b21ae1fe1",
|
||||||
"prevId": "6570ba20-a672-400c-8147-7ba533784918",
|
"prevId": "afe10bff-362b-402c-bdb5-038341692f35",
|
||||||
"tables": {
|
"tables": {
|
||||||
"sandbox": {
|
"sandbox": {
|
||||||
"name": "sandbox",
|
"name": "sandbox",
|
||||||
@ -35,12 +35,36 @@
|
|||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "CURRENT_TIMESTAMP"
|
||||||
|
},
|
||||||
"user_id": {
|
"user_id": {
|
||||||
"name": "user_id",
|
"name": "user_id",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"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": {
|
"indexes": {
|
||||||
@ -94,12 +118,35 @@
|
|||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
},
|
},
|
||||||
"image": {
|
"username": {
|
||||||
"name": "image",
|
"name": "username",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"avatarUrl": {
|
||||||
|
"name": "avatarUrl",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false,
|
"notNull": false,
|
||||||
"autoincrement": 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
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {
|
"indexes": {
|
||||||
@ -109,6 +156,13 @@
|
|||||||
"id"
|
"id"
|
||||||
],
|
],
|
||||||
"isUnique": true
|
"isUnique": true
|
||||||
|
},
|
||||||
|
"user_username_unique": {
|
||||||
|
"name": "user_username_unique",
|
||||||
|
"columns": [
|
||||||
|
"username"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"foreignKeys": {},
|
"foreignKeys": {},
|
||||||
@ -131,6 +185,13 @@
|
|||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"sharedOn": {
|
||||||
|
"name": "sharedOn",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {},
|
"indexes": {},
|
||||||
|
@ -1,168 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "5",
|
|
||||||
"dialect": "sqlite",
|
|
||||||
"id": "5baf10d6-7697-42ba-a11a-ee4c7bd7e91e",
|
|
||||||
"prevId": "9f64104a-4954-40c0-8155-17755ea0a243",
|
|
||||||
"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
|
|
||||||
},
|
|
||||||
"user_id": {
|
|
||||||
"name": "user_id",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"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": {}
|
|
||||||
},
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {
|
|
||||||
"user_id_unique": {
|
|
||||||
"name": "user_id_unique",
|
|
||||||
"columns": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"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": {}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,175 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "5",
|
|
||||||
"dialect": "sqlite",
|
|
||||||
"id": "37e38b82-1494-4818-8c26-b9024cce3fa9",
|
|
||||||
"prevId": "5baf10d6-7697-42ba-a11a-ee4c7bd7e91e",
|
|
||||||
"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
|
|
||||||
},
|
|
||||||
"user_id": {
|
|
||||||
"name": "user_id",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true,
|
|
||||||
"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": {}
|
|
||||||
},
|
|
||||||
"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
|
|
||||||
},
|
|
||||||
"image": {
|
|
||||||
"name": "image",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {
|
|
||||||
"user_id_unique": {
|
|
||||||
"name": "user_id_unique",
|
|
||||||
"columns": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"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,50 +5,29 @@
|
|||||||
{
|
{
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"version": "5",
|
"version": "5",
|
||||||
"when": 1714540200800,
|
"when": 1731288423588,
|
||||||
"tag": "0000_big_rogue",
|
"tag": "0000_cuddly_patriot",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"version": "5",
|
"version": "5",
|
||||||
"when": 1714541190588,
|
"when": 1731290863632,
|
||||||
"tag": "0001_empty_black_knight",
|
"tag": "0001_opposite_newton_destine",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 2,
|
"idx": 2,
|
||||||
"version": "5",
|
"version": "5",
|
||||||
"when": 1714541209173,
|
"when": 1731296235880,
|
||||||
"tag": "0002_sour_ego",
|
"tag": "0002_rainy_fantastic_four",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 3,
|
"idx": 3,
|
||||||
"version": "5",
|
"version": "5",
|
||||||
"when": 1714541233589,
|
"when": 1731297339306,
|
||||||
"tag": "0003_pale_overlord",
|
"tag": "0003_lying_snowbird",
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 4,
|
|
||||||
"version": "5",
|
|
||||||
"when": 1714565073180,
|
|
||||||
"tag": "0004_cuddly_wolf_cub",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 5,
|
|
||||||
"version": "5",
|
|
||||||
"when": 1714950365718,
|
|
||||||
"tag": "0005_last_the_twelve",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 6,
|
|
||||||
"version": "5",
|
|
||||||
"when": 1716432225404,
|
|
||||||
"tag": "0006_lively_mattie_franklin",
|
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
9950
backend/database/package-lock.json
generated
9950
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.0.0"
|
"wrangler": "^3.86.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@paralleldrive/cuid2": "^2.2.2",
|
"@paralleldrive/cuid2": "^2.2.2",
|
||||||
@ -29,4 +29,4 @@
|
|||||||
"itty-router-extras": "^0.4.6",
|
"itty-router-extras": "^0.4.6",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -169,6 +169,7 @@ export default {
|
|||||||
name: sb.name,
|
name: sb.name,
|
||||||
type: sb.type,
|
type: sb.type,
|
||||||
author: sb.author.name,
|
author: sb.author.name,
|
||||||
|
authorAvatarUrl: sb.author.avatarUrl,
|
||||||
sharedOn: r.sharedOn,
|
sharedOn: r.sharedOn,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -282,14 +283,26 @@ export default {
|
|||||||
id: z.string(),
|
id: z.string(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
email: z.string().email(),
|
email: z.string().email(),
|
||||||
|
username: z.string(),
|
||||||
|
avatarUrl: z.string().optional(),
|
||||||
|
createdAt: z.string().optional(),
|
||||||
|
generations: z.number().optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const body = await request.json()
|
const body = await request.json()
|
||||||
const { id, name, email } = userSchema.parse(body)
|
const { id, name, email, username, avatarUrl, createdAt, generations } = userSchema.parse(body)
|
||||||
|
|
||||||
const res = await db
|
const res = await db
|
||||||
.insert(user)
|
.insert(user)
|
||||||
.values({ id, name, email })
|
.values({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
email,
|
||||||
|
username,
|
||||||
|
avatarUrl,
|
||||||
|
createdAt: createdAt ? new Date(createdAt) : new Date(),
|
||||||
|
generations,
|
||||||
|
})
|
||||||
.returning()
|
.returning()
|
||||||
.get()
|
.get()
|
||||||
return json({ res })
|
return json({ res })
|
||||||
@ -303,6 +316,20 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
return methodNotAllowed
|
return methodNotAllowed
|
||||||
}
|
}
|
||||||
|
} else if (path === "/api/user/check-username") {
|
||||||
|
if (method === "GET") {
|
||||||
|
const params = url.searchParams
|
||||||
|
const username = params.get("username")
|
||||||
|
|
||||||
|
if (!username) return invalidRequest
|
||||||
|
|
||||||
|
const exists = await db.query.user.findFirst({
|
||||||
|
where: (user, { eq }) => eq(user.username, username)
|
||||||
|
})
|
||||||
|
|
||||||
|
return json({ exists: !!exists })
|
||||||
|
}
|
||||||
|
return methodNotAllowed
|
||||||
} else return notFound
|
} else return notFound
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { createId } from "@paralleldrive/cuid2"
|
import { createId } from "@paralleldrive/cuid2"
|
||||||
import { relations } from "drizzle-orm"
|
import { relations } from "drizzle-orm"
|
||||||
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"
|
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"
|
||||||
|
import { sql } from "drizzle-orm"
|
||||||
|
|
||||||
export const user = sqliteTable("user", {
|
export const user = sqliteTable("user", {
|
||||||
id: text("id")
|
id: text("id")
|
||||||
@ -9,7 +10,10 @@ export const user = sqliteTable("user", {
|
|||||||
.unique(),
|
.unique(),
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
email: text("email").notNull(),
|
email: text("email").notNull(),
|
||||||
image: text("image"),
|
username: text("username").notNull().unique(),
|
||||||
|
avatarUrl: text("avatarUrl"),
|
||||||
|
createdAt: integer("createdAt", { mode: "timestamp_ms" })
|
||||||
|
.default(sql`CURRENT_TIMESTAMP`),
|
||||||
generations: integer("generations").default(0),
|
generations: integer("generations").default(0),
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -28,10 +32,13 @@ export const sandbox = sqliteTable("sandbox", {
|
|||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
type: text("type").notNull(),
|
type: text("type").notNull(),
|
||||||
visibility: text("visibility", { enum: ["public", "private"] }),
|
visibility: text("visibility", { enum: ["public", "private"] }),
|
||||||
createdAt: integer("createdAt", { mode: "timestamp_ms" }),
|
createdAt: integer("createdAt", { mode: "timestamp_ms" })
|
||||||
|
.default(sql`CURRENT_TIMESTAMP`),
|
||||||
userId: text("user_id")
|
userId: text("user_id")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => user.id),
|
.references(() => user.id),
|
||||||
|
likeCount: integer("likeCount").default(0),
|
||||||
|
viewCount: integer("viewCount").default(0),
|
||||||
})
|
})
|
||||||
|
|
||||||
export type Sandbox = typeof sandbox.$inferSelect
|
export type Sandbox = typeof sandbox.$inferSelect
|
||||||
|
6221
backend/storage/package-lock.json
generated
6221
backend/storage/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -11,13 +11,13 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cloudflare/vitest-pool-workers": "^0.1.0",
|
"@cloudflare/vitest-pool-workers": "^0.1.0",
|
||||||
"@cloudflare/workers-types": "^4.20240419.0",
|
"@cloudflare/workers-types": "^4.20241106.0",
|
||||||
"typescript": "^5.0.4",
|
"typescript": "^5.0.4",
|
||||||
"vitest": "1.3.0",
|
"vitest": "1.3.0",
|
||||||
"wrangler": "^3.0.0"
|
"wrangler": "^3.86.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"p-limit": "^6.1.0",
|
"p-limit": "^6.1.0",
|
||||||
"zod": "^3.23.4"
|
"zod": "^3.23.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { ExecutionContext, R2Bucket, Headers as CFHeaders } from "@cloudflare/workers-types"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
export interface Env {
|
export interface Env {
|
||||||
@ -75,14 +76,13 @@ export default {
|
|||||||
if (obj === null) {
|
if (obj === null) {
|
||||||
return new Response(`${fileId} not found`, { status: 404 })
|
return new Response(`${fileId} not found`, { status: 404 })
|
||||||
}
|
}
|
||||||
const headers = new Headers()
|
const headers = new Headers() as unknown as CFHeaders
|
||||||
headers.set("etag", obj.httpEtag)
|
headers.set("etag", obj.httpEtag)
|
||||||
obj.writeHttpMetadata(headers)
|
obj.writeHttpMetadata(headers)
|
||||||
|
|
||||||
const text = await obj.text()
|
const text = await obj.text()
|
||||||
|
|
||||||
return new Response(text, {
|
return new Response(text, {
|
||||||
headers,
|
headers: Object.fromEntries(headers.entries()),
|
||||||
})
|
})
|
||||||
} else return invalidRequest
|
} else return invalidRequest
|
||||||
} else if (method === "POST") {
|
} else if (method === "POST") {
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||||
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
|
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
|
||||||
"types": [
|
"types": [
|
||||||
"@cloudflare/workers-types/2023-07-01"
|
"@cloudflare/workers-types"
|
||||||
] /* Specify type package names to be included without being referenced in a source file. */,
|
] /* Specify type package names to be included without being referenced in a source file. */,
|
||||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
"resolveJsonModule": true /* Enable importing .json files */,
|
"resolveJsonModule": true /* Enable importing .json files */,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Room } from "@/components/editor/live/room"
|
// import { Room } from "@/components/editor/live/room"
|
||||||
import Loading from "@/components/editor/loading"
|
import Loading from "@/components/editor/loading"
|
||||||
import Navbar from "@/components/editor/navbar"
|
import Navbar from "@/components/editor/navbar"
|
||||||
import { TerminalProvider } from "@/context/TerminalContext"
|
import { TerminalProvider } from "@/context/TerminalContext"
|
||||||
@ -51,7 +51,7 @@ const getSharedUsers = async (usersToSandboxes: UsersToSandboxes[]) => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
const userData: User = await userRes.json()
|
const userData: User = await userRes.json()
|
||||||
return { id: userData.id, name: userData.name }
|
return { id: userData.id, name: userData.name, avatarUrl: userData.avatarUrl }
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -89,18 +89,18 @@ export default async function CodePage({ params }: { params: { id: string } }) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="overflow-hidden overscroll-none w-screen flex flex-col h-screen bg-background">
|
<div className="overflow-hidden overscroll-none w-screen flex flex-col h-screen bg-background">
|
||||||
<Room id={sandboxId}>
|
{/* <Room id={sandboxId}> */}
|
||||||
<TerminalProvider>
|
<TerminalProvider>
|
||||||
<Navbar
|
<Navbar
|
||||||
userData={userData}
|
userData={userData}
|
||||||
sandboxData={sandboxData}
|
sandboxData={sandboxData}
|
||||||
shared={shared}
|
shared={shared as { id: string; name: string; avatarUrl: string }[]}
|
||||||
/>
|
/>
|
||||||
<div className="w-screen flex grow">
|
<div className="w-screen flex grow">
|
||||||
<CodeEditor userData={userData} sandboxData={sandboxData} />
|
<CodeEditor userData={userData} sandboxData={sandboxData} />
|
||||||
</div>
|
</div>
|
||||||
</TerminalProvider>
|
</TerminalProvider>
|
||||||
</Room>
|
{/* </Room> */}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -35,6 +35,7 @@ export default async function DashboardPage() {
|
|||||||
type: "react" | "node"
|
type: "react" | "node"
|
||||||
author: string
|
author: string
|
||||||
sharedOn: Date
|
sharedOn: Date
|
||||||
|
authorAvatarUrl: string
|
||||||
}[]
|
}[]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { User } from "@/lib/types"
|
import { User } from "@/lib/types"
|
||||||
import { currentUser } from "@clerk/nextjs"
|
import { currentUser } from "@clerk/nextjs"
|
||||||
import { redirect } from "next/navigation"
|
import { redirect } from "next/navigation"
|
||||||
|
import { generateUniqueUsername } from "@/lib/username-generator";
|
||||||
|
|
||||||
export default async function AppAuthLayout({
|
export default async function AppAuthLayout({
|
||||||
children,
|
children,
|
||||||
@ -24,6 +25,25 @@ export default async function AppAuthLayout({
|
|||||||
const dbUserJSON = (await dbUser.json()) as User
|
const dbUserJSON = (await dbUser.json()) as User
|
||||||
|
|
||||||
if (!dbUserJSON.id) {
|
if (!dbUserJSON.id) {
|
||||||
|
// Try to get GitHub username if available
|
||||||
|
const githubUsername = user.externalAccounts.find(
|
||||||
|
account => account.provider === "github"
|
||||||
|
)?.username;
|
||||||
|
|
||||||
|
const username = githubUsername || await generateUniqueUsername(async (username) => {
|
||||||
|
// Check if username exists in database
|
||||||
|
const userCheck = await fetch(
|
||||||
|
`${process.env.NEXT_PUBLIC_DATABASE_WORKER_URL}/api/user/check-username?username=${username}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `${process.env.NEXT_PUBLIC_WORKERS_KEY}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const exists = await userCheck.json()
|
||||||
|
return exists.exists
|
||||||
|
});
|
||||||
|
|
||||||
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`,
|
||||||
{
|
{
|
||||||
@ -36,9 +56,20 @@ export default async function AppAuthLayout({
|
|||||||
id: user.id,
|
id: user.id,
|
||||||
name: user.firstName + " " + user.lastName,
|
name: user.firstName + " " + user.lastName,
|
||||||
email: user.emailAddresses[0].emailAddress,
|
email: user.emailAddresses[0].emailAddress,
|
||||||
|
username: username,
|
||||||
|
avatarUrl: user.imageUrl || null,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
const error = await res.text();
|
||||||
|
console.error("Failed to create user:", error);
|
||||||
|
} else {
|
||||||
|
const data = await res.json();
|
||||||
|
console.log("User created successfully:", data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return <>{children}</>
|
return <>{children}</>
|
||||||
|
@ -1,57 +1,61 @@
|
|||||||
import { colors } from "@/lib/colors"
|
// import { colors } from "@/lib/colors"
|
||||||
import { User } from "@/lib/types"
|
// import { User } from "@/lib/types"
|
||||||
import { currentUser } from "@clerk/nextjs"
|
import { currentUser } from "@clerk/nextjs"
|
||||||
import { Liveblocks } from "@liveblocks/node"
|
// import { Liveblocks } from "@liveblocks/node"
|
||||||
import { NextRequest } from "next/server"
|
import { NextRequest } from "next/server"
|
||||||
|
|
||||||
const API_KEY = process.env.LIVEBLOCKS_SECRET_KEY!
|
// const API_KEY = process.env.LIVEBLOCKS_SECRET_KEY!
|
||||||
|
|
||||||
const liveblocks = new Liveblocks({
|
// const liveblocks = new Liveblocks({
|
||||||
secret: API_KEY!,
|
// secret: API_KEY!,
|
||||||
})
|
// })
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
const clerkUser = await currentUser()
|
// Temporarily return unauthorized while Liveblocks is disabled
|
||||||
|
return new Response("Liveblocks collaboration temporarily disabled", { status: 503 })
|
||||||
|
|
||||||
if (!clerkUser) {
|
// Original implementation commented out:
|
||||||
return new Response("Unauthorized", { status: 401 })
|
// const clerkUser = await currentUser()
|
||||||
}
|
//
|
||||||
|
// if (!clerkUser) {
|
||||||
const res = await fetch(
|
// return new Response("Unauthorized", { status: 401 })
|
||||||
`${process.env.NEXT_PUBLIC_DATABASE_WORKER_URL}/api/user?id=${clerkUser.id}`,
|
// }
|
||||||
{
|
//
|
||||||
headers: {
|
// const res = await fetch(
|
||||||
Authorization: `${process.env.NEXT_PUBLIC_WORKERS_KEY}`,
|
// `${process.env.NEXT_PUBLIC_DATABASE_WORKER_URL}/api/user?id=${clerkUser.id}`,
|
||||||
},
|
// {
|
||||||
}
|
// headers: {
|
||||||
)
|
// Authorization: `${process.env.NEXT_PUBLIC_WORKERS_KEY}`,
|
||||||
const user = (await res.json()) as User
|
// },
|
||||||
|
// }
|
||||||
const colorNames = Object.keys(colors)
|
// )
|
||||||
const randomColor = colorNames[
|
// const user = (await res.json()) as User
|
||||||
Math.floor(Math.random() * colorNames.length)
|
//
|
||||||
] as keyof typeof colors
|
// const colorNames = Object.keys(colors)
|
||||||
const code = colors[randomColor]
|
// const randomColor = colorNames[
|
||||||
|
// Math.floor(Math.random() * colorNames.length)
|
||||||
// Create a session for the current user
|
// ] as keyof typeof colors
|
||||||
// userInfo is made available in Liveblocks presence hooks, e.g. useOthers
|
// const code = colors[randomColor]
|
||||||
const session = liveblocks.prepareSession(user.id, {
|
//
|
||||||
userInfo: {
|
// // Create a session for the current user
|
||||||
name: user.name,
|
// // userInfo is made available in Liveblocks presence hooks, e.g. useOthers
|
||||||
email: user.email,
|
// const session = liveblocks.prepareSession(user.id, {
|
||||||
color: randomColor,
|
// userInfo: {
|
||||||
},
|
// name: user.name,
|
||||||
})
|
// email: user.email,
|
||||||
|
// color: randomColor,
|
||||||
// Give the user access to the room
|
// },
|
||||||
user.sandbox.forEach((sandbox) => {
|
// })
|
||||||
session.allow(`${sandbox.id}`, session.FULL_ACCESS)
|
//
|
||||||
})
|
// // Give the user access to the room
|
||||||
user.usersToSandboxes.forEach((userToSandbox) => {
|
// user.sandbox.forEach((sandbox) => {
|
||||||
session.allow(`${userToSandbox.sandboxId}`, session.FULL_ACCESS)
|
// session.allow(`${sandbox.id}`, session.FULL_ACCESS)
|
||||||
})
|
// })
|
||||||
|
// user.usersToSandboxes.forEach((userToSandbox) => {
|
||||||
// Authorize the user and return the result
|
// session.allow(`${userToSandbox.sandboxId}`, session.FULL_ACCESS)
|
||||||
const { body, status } = await session.authorize()
|
// })
|
||||||
return new Response(body, { status })
|
//
|
||||||
|
// // Authorize the user and return the result
|
||||||
|
// const { body, status } = await session.authorize()
|
||||||
|
// return new Response(body, { status })
|
||||||
}
|
}
|
||||||
|
@ -18,11 +18,38 @@ export default function AboutModal({
|
|||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>About this project</DialogTitle>
|
<DialogTitle>Help & Support</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="text-sm text-muted-foreground">
|
<div className="space-y-4">
|
||||||
Sandbox is an open-source cloud-based code editing environment with
|
{/* <div className="text-sm text-muted-foreground">
|
||||||
custom AI code autocompletion and real-time collaboration.
|
Sandbox is an open-source cloud-based code editing environment with
|
||||||
|
custom AI code autocompletion and real-time collaboration.
|
||||||
|
</div> */}
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
Get help and support through our Discord community or by creating issues on GitHub:
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="text-sm">
|
||||||
|
<a
|
||||||
|
href="https://discord.gitwit.dev/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-primary hover:underline"
|
||||||
|
>
|
||||||
|
Join our Discord community →
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="text-sm">
|
||||||
|
<a
|
||||||
|
href="https://github.com/jamesmurdza/sandbox/issues"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-primary hover:underline"
|
||||||
|
>
|
||||||
|
Report issues on GitHub →
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
@ -25,6 +25,7 @@ export default function Dashboard({
|
|||||||
type: "react" | "node"
|
type: "react" | "node"
|
||||||
author: string
|
author: string
|
||||||
sharedOn: Date
|
sharedOn: Date
|
||||||
|
authorAvatarUrl?: string
|
||||||
}[]
|
}[]
|
||||||
}) {
|
}) {
|
||||||
const [screen, setScreen] = useState<TScreen>("projects")
|
const [screen, setScreen] = useState<TScreen>("projects")
|
||||||
@ -77,14 +78,14 @@ export default function Dashboard({
|
|||||||
<FolderDot className="w-4 h-4 mr-2" />
|
<FolderDot className="w-4 h-4 mr-2" />
|
||||||
My Projects
|
My Projects
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
{/* <Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => setScreen("shared")}
|
onClick={() => setScreen("shared")}
|
||||||
className={activeScreen("shared")}
|
className={activeScreen("shared")}
|
||||||
>
|
>
|
||||||
<Users className="w-4 h-4 mr-2" />
|
<Users className="w-4 h-4 mr-2" />
|
||||||
Shared With Me
|
Shared With Me
|
||||||
</Button>
|
</Button> */}
|
||||||
{/* <Button
|
{/* <Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => setScreen("settings")}
|
onClick={() => setScreen("settings")}
|
||||||
@ -110,7 +111,7 @@ export default function Dashboard({
|
|||||||
className="justify-start font-normal text-muted-foreground"
|
className="justify-start font-normal text-muted-foreground"
|
||||||
>
|
>
|
||||||
<HelpCircle className="w-4 h-4 mr-2" />
|
<HelpCircle className="w-4 h-4 mr-2" />
|
||||||
About
|
Help
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -121,7 +122,12 @@ export default function Dashboard({
|
|||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
) : screen === "shared" ? (
|
) : screen === "shared" ? (
|
||||||
<DashboardSharedWithMe shared={shared} />
|
<DashboardSharedWithMe
|
||||||
|
shared={shared.map((item) => ({
|
||||||
|
...item,
|
||||||
|
authorAvatarUrl: item.authorAvatarUrl || "",
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
) : screen === "settings" ? null : null}
|
) : screen === "settings" ? null : null}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -11,6 +11,7 @@ import Image from "next/image"
|
|||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import Avatar from "../ui/avatar"
|
import Avatar from "../ui/avatar"
|
||||||
import Button from "../ui/customButton"
|
import Button from "../ui/customButton"
|
||||||
|
import { projectTemplates } from "@/lib/data"
|
||||||
|
|
||||||
export default function DashboardSharedWithMe({
|
export default function DashboardSharedWithMe({
|
||||||
shared,
|
shared,
|
||||||
@ -18,8 +19,9 @@ export default function DashboardSharedWithMe({
|
|||||||
shared: {
|
shared: {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
type: "react" | "node"
|
type: string
|
||||||
author: string
|
author: string
|
||||||
|
authorAvatarUrl: string
|
||||||
sharedOn: Date
|
sharedOn: Date
|
||||||
}[]
|
}[]
|
||||||
}) {
|
}) {
|
||||||
@ -45,9 +47,7 @@ export default function DashboardSharedWithMe({
|
|||||||
<Image
|
<Image
|
||||||
alt=""
|
alt=""
|
||||||
src={
|
src={
|
||||||
sandbox.type === "react"
|
projectTemplates.find((p) => p.id === sandbox.type)?.icon ?? "/project-icons/node.svg"
|
||||||
? "/project-icons/react.svg"
|
|
||||||
: "/project-icons/node.svg"
|
|
||||||
}
|
}
|
||||||
width={20}
|
width={20}
|
||||||
height={20}
|
height={20}
|
||||||
@ -58,7 +58,11 @@ export default function DashboardSharedWithMe({
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Avatar name={sandbox.author} className="mr-2" />
|
<Avatar
|
||||||
|
name={sandbox.author}
|
||||||
|
avatarUrl={sandbox.authorAvatarUrl}
|
||||||
|
className="mr-2"
|
||||||
|
/>
|
||||||
{sandbox.author}
|
{sandbox.author}
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
@ -7,11 +7,11 @@ import * as monaco from "monaco-editor"
|
|||||||
import { useCallback, useEffect, useRef, useState } from "react"
|
import { useCallback, useEffect, useRef, useState } from "react"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
|
|
||||||
import { TypedLiveblocksProvider, useRoom, useSelf } from "@/liveblocks.config"
|
// import { TypedLiveblocksProvider, useRoom, useSelf } from "@/liveblocks.config"
|
||||||
import LiveblocksProvider from "@liveblocks/yjs"
|
// import LiveblocksProvider from "@liveblocks/yjs"
|
||||||
import { MonacoBinding } from "y-monaco"
|
// import { MonacoBinding } from "y-monaco"
|
||||||
import { Awareness } from "y-protocols/awareness"
|
// import { Awareness } from "y-protocols/awareness"
|
||||||
import * as Y from "yjs"
|
// import * as Y from "yjs"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ResizableHandle,
|
ResizableHandle,
|
||||||
@ -46,7 +46,7 @@ import { Button } from "../ui/button"
|
|||||||
import Tab from "../ui/tab"
|
import Tab from "../ui/tab"
|
||||||
import AIChat from "./AIChat"
|
import AIChat from "./AIChat"
|
||||||
import GenerateInput from "./generate"
|
import GenerateInput from "./generate"
|
||||||
import { Cursors } from "./live/cursors"
|
// import { Cursors } from "./live/cursors"
|
||||||
import DisableAccessModal from "./live/disableModal"
|
import DisableAccessModal from "./live/disableModal"
|
||||||
import Loading from "./loading"
|
import Loading from "./loading"
|
||||||
import PreviewWindow from "./preview"
|
import PreviewWindow from "./preview"
|
||||||
@ -147,20 +147,20 @@ export default function CodeEditor({
|
|||||||
const isOwner = sandboxData.userId === userData.id
|
const isOwner = sandboxData.userId === userData.id
|
||||||
const clerk = useClerk()
|
const clerk = useClerk()
|
||||||
|
|
||||||
// Liveblocks hooks
|
// // Liveblocks hooks
|
||||||
const room = useRoom()
|
// const room = useRoom()
|
||||||
const [provider, setProvider] = useState<TypedLiveblocksProvider>()
|
// const [provider, setProvider] = useState<TypedLiveblocksProvider>()
|
||||||
const userInfo = useSelf((me) => me.info)
|
// const userInfo = useSelf((me) => me.info)
|
||||||
|
|
||||||
// Liveblocks providers map to prevent reinitializing providers
|
// // Liveblocks providers map to prevent reinitializing providers
|
||||||
type ProviderData = {
|
// type ProviderData = {
|
||||||
provider: LiveblocksProvider<never, never, never, never>
|
// provider: LiveblocksProvider<never, never, never, never>
|
||||||
yDoc: Y.Doc
|
// yDoc: Y.Doc
|
||||||
yText: Y.Text
|
// yText: Y.Text
|
||||||
binding?: MonacoBinding
|
// binding?: MonacoBinding
|
||||||
onSync: (isSynced: boolean) => void
|
// onSync: (isSynced: boolean) => void
|
||||||
}
|
// }
|
||||||
const providersMap = useRef(new Map<string, ProviderData>())
|
// const providersMap = useRef(new Map<string, ProviderData>())
|
||||||
|
|
||||||
// Refs for libraries / features
|
// Refs for libraries / features
|
||||||
const editorContainerRef = useRef<HTMLDivElement>(null)
|
const editorContainerRef = useRef<HTMLDivElement>(null)
|
||||||
@ -541,8 +541,6 @@ export default function CodeEditor({
|
|||||||
tab.id === activeFileId ? { ...tab, saved: true } : tab
|
tab.id === activeFileId ? { ...tab, saved: true } : tab
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
console.log(`Saving file...${activeFileId}`)
|
|
||||||
console.log(`Saving file...${content}`)
|
|
||||||
socket?.emit("saveFile", { fileId: activeFileId, body: content })
|
socket?.emit("saveFile", { fileId: activeFileId, body: content })
|
||||||
}
|
}
|
||||||
}, Number(process.env.FILE_SAVE_DEBOUNCE_DELAY) || 1000),
|
}, Number(process.env.FILE_SAVE_DEBOUNCE_DELAY) || 1000),
|
||||||
@ -573,82 +571,82 @@ export default function CodeEditor({
|
|||||||
}
|
}
|
||||||
}, [activeFileId, tabs, debouncedSaveData, setIsAIChatOpen, editorRef])
|
}, [activeFileId, tabs, debouncedSaveData, setIsAIChatOpen, editorRef])
|
||||||
|
|
||||||
// Liveblocks live collaboration setup effect
|
// // Liveblocks live collaboration setup effect
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
const tab = tabs.find((t) => t.id === activeFileId)
|
// const tab = tabs.find((t) => t.id === activeFileId)
|
||||||
const model = editorRef?.getModel()
|
// const model = editorRef?.getModel()
|
||||||
|
|
||||||
if (!editorRef || !tab || !model) return
|
// if (!editorRef || !tab || !model) return
|
||||||
|
|
||||||
let providerData: ProviderData
|
// let providerData: ProviderData
|
||||||
|
|
||||||
// When a file is opened for the first time, create a new provider and store in providersMap.
|
// // When a file is opened for the first time, create a new provider and store in providersMap.
|
||||||
if (!providersMap.current.has(tab.id)) {
|
// if (!providersMap.current.has(tab.id)) {
|
||||||
const yDoc = new Y.Doc()
|
// const yDoc = new Y.Doc()
|
||||||
const yText = yDoc.getText(tab.id)
|
// const yText = yDoc.getText(tab.id)
|
||||||
const yProvider = new LiveblocksProvider(room, yDoc)
|
// const yProvider = new LiveblocksProvider(room, yDoc)
|
||||||
|
|
||||||
// Inserts the file content into the editor once when the tab is changed.
|
// // Inserts the file content into the editor once when the tab is changed.
|
||||||
const onSync = (isSynced: boolean) => {
|
// const onSync = (isSynced: boolean) => {
|
||||||
if (isSynced) {
|
// if (isSynced) {
|
||||||
const text = yText.toString()
|
// const text = yText.toString()
|
||||||
if (text === "") {
|
// if (text === "") {
|
||||||
if (activeFileContent) {
|
// if (activeFileContent) {
|
||||||
yText.insert(0, activeFileContent)
|
// yText.insert(0, activeFileContent)
|
||||||
} else {
|
// } else {
|
||||||
setTimeout(() => {
|
// setTimeout(() => {
|
||||||
yText.insert(0, editorRef.getValue())
|
// yText.insert(0, editorRef.getValue())
|
||||||
}, 0)
|
// }, 0)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
yProvider.on("sync", onSync)
|
// yProvider.on("sync", onSync)
|
||||||
|
|
||||||
// Save the provider to the map.
|
// // Save the provider to the map.
|
||||||
providerData = { provider: yProvider, yDoc, yText, onSync }
|
// providerData = { provider: yProvider, yDoc, yText, onSync }
|
||||||
providersMap.current.set(tab.id, providerData)
|
// providersMap.current.set(tab.id, providerData)
|
||||||
} else {
|
// } else {
|
||||||
// When a tab is opened that has been open before, reuse the existing provider.
|
// // When a tab is opened that has been open before, reuse the existing provider.
|
||||||
providerData = providersMap.current.get(tab.id)!
|
// providerData = providersMap.current.get(tab.id)!
|
||||||
}
|
// }
|
||||||
|
|
||||||
const binding = new MonacoBinding(
|
// const binding = new MonacoBinding(
|
||||||
providerData.yText,
|
// providerData.yText,
|
||||||
model,
|
// model,
|
||||||
new Set([editorRef]),
|
// new Set([editorRef]),
|
||||||
providerData.provider.awareness as unknown as Awareness
|
// providerData.provider.awareness as unknown as Awareness
|
||||||
)
|
// )
|
||||||
|
|
||||||
providerData.binding = binding
|
// providerData.binding = binding
|
||||||
setProvider(providerData.provider)
|
// setProvider(providerData.provider)
|
||||||
|
|
||||||
return () => {
|
// return () => {
|
||||||
// Cleanup logic
|
// // Cleanup logic
|
||||||
if (binding) {
|
// if (binding) {
|
||||||
binding.destroy()
|
// binding.destroy()
|
||||||
}
|
// }
|
||||||
if (providerData.binding) {
|
// if (providerData.binding) {
|
||||||
providerData.binding = undefined
|
// providerData.binding = undefined
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}, [room, activeFileContent])
|
// }, [room, activeFileContent])
|
||||||
|
|
||||||
// Added this effect to clean up when the component unmounts
|
// // Added this effect to clean up when the component unmounts
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
return () => {
|
// return () => {
|
||||||
// Clean up all providers when the component unmounts
|
// // Clean up all providers when the component unmounts
|
||||||
providersMap.current.forEach((data) => {
|
// providersMap.current.forEach((data) => {
|
||||||
if (data.binding) {
|
// if (data.binding) {
|
||||||
data.binding.destroy()
|
// data.binding.destroy()
|
||||||
}
|
// }
|
||||||
data.provider.disconnect()
|
// data.provider.disconnect()
|
||||||
data.yDoc.destroy()
|
// data.yDoc.destroy()
|
||||||
})
|
// })
|
||||||
providersMap.current.clear()
|
// providersMap.current.clear()
|
||||||
}
|
// }
|
||||||
}, [])
|
// }, [])
|
||||||
|
|
||||||
// Connection/disconnection effect
|
// Connection/disconnection effect
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -1090,9 +1088,9 @@ export default function CodeEditor({
|
|||||||
) : // Note clerk.loaded is required here due to a bug: https://github.com/clerk/javascript/issues/1643
|
) : // Note clerk.loaded is required here due to a bug: https://github.com/clerk/javascript/issues/1643
|
||||||
clerk.loaded ? (
|
clerk.loaded ? (
|
||||||
<>
|
<>
|
||||||
{provider && userInfo ? (
|
{/* {provider && userInfo ? (
|
||||||
<Cursors yProvider={provider} userInfo={userInfo} />
|
<Cursors yProvider={provider} userInfo={userInfo} />
|
||||||
) : null}
|
) : null} */}
|
||||||
<Editor
|
<Editor
|
||||||
height="100%"
|
height="100%"
|
||||||
language={editorLanguage}
|
language={editorLanguage}
|
||||||
|
@ -9,7 +9,7 @@ import { Pencil, Users } from "lucide-react"
|
|||||||
import Image from "next/image"
|
import Image from "next/image"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import { Avatars } from "../live/avatars"
|
// import { Avatars } from "../live/avatars"
|
||||||
import DeployButtonModal from "./deploy"
|
import DeployButtonModal from "./deploy"
|
||||||
import EditSandboxModal from "./edit"
|
import EditSandboxModal from "./edit"
|
||||||
import RunButtonModal from "./run"
|
import RunButtonModal from "./run"
|
||||||
@ -23,7 +23,7 @@ export default function Navbar({
|
|||||||
}: {
|
}: {
|
||||||
userData: User
|
userData: User
|
||||||
sandboxData: Sandbox
|
sandboxData: Sandbox
|
||||||
shared: { id: string; name: string }[]
|
shared: { id: string; name: string; avatarUrl: string }[]
|
||||||
}) {
|
}) {
|
||||||
const [isEditOpen, setIsEditOpen] = useState(false)
|
const [isEditOpen, setIsEditOpen] = useState(false)
|
||||||
const [isShareOpen, setIsShareOpen] = useState(false)
|
const [isShareOpen, setIsShareOpen] = useState(false)
|
||||||
@ -70,15 +70,15 @@ export default function Navbar({
|
|||||||
sandboxData={sandboxData}
|
sandboxData={sandboxData}
|
||||||
/>
|
/>
|
||||||
<div className="flex items-center h-full space-x-4">
|
<div className="flex items-center h-full space-x-4">
|
||||||
<Avatars />
|
{/* <Avatars /> */}
|
||||||
|
|
||||||
{isOwner ? (
|
{isOwner ? (
|
||||||
<>
|
<>
|
||||||
<DeployButtonModal data={sandboxData} userData={userData} />
|
<DeployButtonModal data={sandboxData} userData={userData} />
|
||||||
<Button variant="outline" onClick={() => setIsShareOpen(true)}>
|
{/* <Button variant="outline" onClick={() => setIsShareOpen(true)}>
|
||||||
<Users className="w-4 h-4 mr-2" />
|
<Users className="w-4 h-4 mr-2" />
|
||||||
Share
|
Share
|
||||||
</Button>
|
</Button> */}
|
||||||
<DownloadButton name={sandboxData.name} /></>
|
<DownloadButton name={sandboxData.name} /></>
|
||||||
) : null}
|
) : null}
|
||||||
<ThemeSwitcher />
|
<ThemeSwitcher />
|
||||||
|
@ -43,6 +43,7 @@ export default function ShareSandboxModal({
|
|||||||
shared: {
|
shared: {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
|
avatarUrl: string
|
||||||
}[]
|
}[]
|
||||||
}) {
|
}) {
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
@ -142,7 +143,11 @@ export default function ShareSandboxModal({
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{shared.map((user) => (
|
{shared.map((user) => (
|
||||||
<SharedUser key={user.id} user={user} sandboxId={data.id} />
|
<SharedUser
|
||||||
|
key={user.id}
|
||||||
|
user={user}
|
||||||
|
sandboxId={data.id}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,7 +10,7 @@ export default function SharedUser({
|
|||||||
user,
|
user,
|
||||||
sandboxId,
|
sandboxId,
|
||||||
}: {
|
}: {
|
||||||
user: { id: string; name: string }
|
user: { id: string; name: string; avatarUrl: string }
|
||||||
sandboxId: string
|
sandboxId: string
|
||||||
}) {
|
}) {
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
@ -24,7 +24,7 @@ export default function SharedUser({
|
|||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Avatar name={user.name} className="mr-2" />
|
<Avatar name={user.name} avatarUrl={user.avatarUrl} className="mr-2" />
|
||||||
{user.name}
|
{user.name}
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
|
@ -45,9 +45,14 @@ export default function Landing() {
|
|||||||
<h1 className="text-2xl font-medium text-center mt-16">
|
<h1 className="text-2xl font-medium text-center mt-16">
|
||||||
A Collaborative + AI-Powered Code Environment
|
A Collaborative + AI-Powered Code Environment
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-muted-foreground mt-4 text-center ">
|
{/* <p className="text-muted-foreground mt-4 text-center ">
|
||||||
Sandbox is an open-source cloud-based code editing environment with
|
Sandbox is an open-source cloud-based code editing environment with
|
||||||
custom AI code autocompletion and real-time collaboration.
|
custom AI code autocompletion and real-time collaboration.
|
||||||
|
</p> */}
|
||||||
|
<p className="text-muted-foreground mt-4 text-center ">
|
||||||
|
A cloud-based code editor featuring real-time collaboration,
|
||||||
|
intelligent code autocompletion, and an AI assistant to help you code
|
||||||
|
faster and smarter.
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-8 flex space-x-4">
|
<div className="mt-8 flex space-x-4">
|
||||||
<Link href="/sign-up">
|
<Link href="/sign-up">
|
||||||
|
59
frontend/components/ui/alert.tsx
Normal file
59
frontend/components/ui/alert.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const alertVariants = cva(
|
||||||
|
"relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-background text-foreground",
|
||||||
|
destructive:
|
||||||
|
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const Alert = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
|
||||||
|
>(({ className, variant, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
role="alert"
|
||||||
|
className={cn(alertVariants({ variant }), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Alert.displayName = "Alert"
|
||||||
|
|
||||||
|
const AlertTitle = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLHeadingElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<h5
|
||||||
|
ref={ref}
|
||||||
|
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertTitle.displayName = "AlertTitle"
|
||||||
|
|
||||||
|
const AlertDescription = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm [&_p]:leading-relaxed", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDescription.displayName = "AlertDescription"
|
||||||
|
|
||||||
|
export { Alert, AlertTitle, AlertDescription }
|
@ -1,23 +1,42 @@
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
import Image from "next/image"
|
||||||
|
|
||||||
export default function Avatar({
|
export default function Avatar({
|
||||||
name,
|
name,
|
||||||
|
avatarUrl,
|
||||||
className,
|
className,
|
||||||
}: {
|
}: {
|
||||||
name: string
|
name: string
|
||||||
|
avatarUrl?: string | null
|
||||||
className?: string
|
className?: string
|
||||||
}) {
|
}) {
|
||||||
|
// Generate initials from name if no avatarUrl is provided
|
||||||
|
const initials = name
|
||||||
|
? name
|
||||||
|
.split(" ")
|
||||||
|
.slice(0, 2)
|
||||||
|
.map((letter) => letter[0].toUpperCase())
|
||||||
|
.join("")
|
||||||
|
: "?"
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
className,
|
className,
|
||||||
"w-5 h-5 font-mono rounded-full overflow-hidden bg-gradient-to-t from-neutral-800 to-neutral-600 flex items-center justify-center text-[0.5rem] font-medium"
|
"w-9 h-9 font-mono rounded-full overflow-hidden bg-gradient-to-t from-neutral-800 to-neutral-600 flex items-center justify-center text-sm font-medium"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{name
|
{avatarUrl ? (
|
||||||
.split(" ")
|
<Image
|
||||||
.slice(0, 2)
|
src={avatarUrl}
|
||||||
.map((letter) => letter[0].toUpperCase())}
|
alt={name || "User"}
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
initials
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
28
frontend/components/ui/progress.tsx
Normal file
28
frontend/components/ui/progress.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as ProgressPrimitive from "@radix-ui/react-progress"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Progress = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ProgressPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
|
||||||
|
>(({ className, value, ...props }, ref) => (
|
||||||
|
<ProgressPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative h-2 w-full overflow-hidden rounded-full bg-primary/20",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ProgressPrimitive.Indicator
|
||||||
|
className="h-full w-full flex-1 bg-primary transition-all"
|
||||||
|
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
||||||
|
/>
|
||||||
|
</ProgressPrimitive.Root>
|
||||||
|
))
|
||||||
|
Progress.displayName = ProgressPrimitive.Root.displayName
|
||||||
|
|
||||||
|
export { Progress }
|
32
frontend/components/ui/tooltip.tsx
Normal file
32
frontend/components/ui/tooltip.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const TooltipProvider = TooltipPrimitive.Provider
|
||||||
|
|
||||||
|
const Tooltip = TooltipPrimitive.Root
|
||||||
|
|
||||||
|
const TooltipTrigger = TooltipPrimitive.Trigger
|
||||||
|
|
||||||
|
const TooltipContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
||||||
|
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||||
|
<TooltipPrimitive.Portal>
|
||||||
|
<TooltipPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn(
|
||||||
|
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</TooltipPrimitive.Portal>
|
||||||
|
))
|
||||||
|
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
||||||
|
|
||||||
|
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
@ -11,6 +11,7 @@ import { User } from "@/lib/types"
|
|||||||
import { useClerk } from "@clerk/nextjs"
|
import { useClerk } from "@clerk/nextjs"
|
||||||
import { LogOut, Sparkles } from "lucide-react"
|
import { LogOut, Sparkles } from "lucide-react"
|
||||||
import { useRouter } from "next/navigation"
|
import { useRouter } from "next/navigation"
|
||||||
|
import Avatar from "./avatar"
|
||||||
|
|
||||||
export default function UserButton({ userData }: { userData: User }) {
|
export default function UserButton({ userData }: { userData: User }) {
|
||||||
if (!userData) return null
|
if (!userData) return null
|
||||||
@ -21,13 +22,7 @@ export default function UserButton({ userData }: { userData: User }) {
|
|||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger>
|
<DropdownMenuTrigger>
|
||||||
<div className="w-9 h-9 font-mono rounded-full overflow-hidden bg-gradient-to-t from-neutral-800 to-neutral-600 flex items-center justify-center text-sm font-medium">
|
<Avatar name={userData.name} avatarUrl={userData.avatarUrl} />
|
||||||
{userData.name &&
|
|
||||||
userData.name
|
|
||||||
.split(" ")
|
|
||||||
.slice(0, 2)
|
|
||||||
.map((name) => name[0].toUpperCase())}
|
|
||||||
</div>
|
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent className="w-48" align="end">
|
<DropdownMenuContent className="w-48" align="end">
|
||||||
<div className="py-1.5 px-2 w-full">
|
<div className="py-1.5 px-2 w-full">
|
||||||
|
@ -16,7 +16,7 @@ export const projectTemplates: {
|
|||||||
id: "vanillajs",
|
id: "vanillajs",
|
||||||
name: "HTML/JS",
|
name: "HTML/JS",
|
||||||
icon: "/project-icons/more.svg",
|
icon: "/project-icons/more.svg",
|
||||||
description: "More coming soon, feel free to contribute on GitHub",
|
description: "A simple HTML/JS project for building web apps",
|
||||||
disabled: false,
|
disabled: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -4,6 +4,9 @@ export type User = {
|
|||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
email: string
|
email: string
|
||||||
|
username: string
|
||||||
|
avatarUrl: string | null
|
||||||
|
createdAt: Date
|
||||||
generations: number
|
generations: number
|
||||||
sandbox: Sandbox[]
|
sandbox: Sandbox[]
|
||||||
usersToSandboxes: UsersToSandboxes[]
|
usersToSandboxes: UsersToSandboxes[]
|
||||||
|
82
frontend/lib/username-generator.ts
Normal file
82
frontend/lib/username-generator.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
// Constants for username generation
|
||||||
|
const WORDS = {
|
||||||
|
adjectives: [
|
||||||
|
"azure", "crimson", "golden", "silver", "violet", "emerald", "cobalt", "amber", "coral", "jade",
|
||||||
|
"cyber", "digital", "quantum", "neural", "binary", "cosmic", "stellar", "atomic", "crypto", "nano",
|
||||||
|
"swift", "brave", "clever", "wise", "noble", "rapid", "bright", "sharp", "keen", "bold",
|
||||||
|
"dynamic", "epic", "mega", "ultra", "hyper", "super", "prime", "elite", "alpha", "omega",
|
||||||
|
"pixel", "vector", "sonic", "laser", "matrix", "nexus", "proxy", "cloud", "data", "tech",
|
||||||
|
],
|
||||||
|
nouns: [
|
||||||
|
"coder", "hacker", "dev", "ninja", "guru", "wizard", "admin", "mod", "chief", "boss",
|
||||||
|
"wolf", "eagle", "phoenix", "dragon", "tiger", "falcon", "shark", "lion", "hawk", "bear",
|
||||||
|
"byte", "bit", "node", "stack", "cache", "chip", "core", "net", "web", "app",
|
||||||
|
"star", "nova", "pulsar", "comet", "nebula", "quasar", "cosmos", "orbit", "astro", "solar",
|
||||||
|
"mind", "soul", "spark", "pulse", "force", "power", "wave", "storm", "flash", "surge",
|
||||||
|
],
|
||||||
|
prefixes: [
|
||||||
|
"the", "mr", "ms", "dr", "pro", "master", "lord", "captain", "chief", "agent",
|
||||||
|
],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// Helper function to get random element from array
|
||||||
|
const getRandomElement = <T>(array: readonly T[]): T => {
|
||||||
|
return array[Math.floor(Math.random() * array.length)];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Username pattern generators
|
||||||
|
const usernamePatterns = {
|
||||||
|
basic: (): string => {
|
||||||
|
const adjective = getRandomElement(WORDS.adjectives);
|
||||||
|
const noun = getRandomElement(WORDS.nouns);
|
||||||
|
const number = Math.floor(Math.random() * 10000);
|
||||||
|
return `${adjective}${noun}${number}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
prefixed: (): string => {
|
||||||
|
const prefix = getRandomElement(WORDS.prefixes);
|
||||||
|
const noun = getRandomElement(WORDS.nouns);
|
||||||
|
const number = Math.floor(Math.random() * 100);
|
||||||
|
return `${prefix}${noun}${number}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
doubleAdjective: (): string => {
|
||||||
|
const adj1 = getRandomElement(WORDS.adjectives);
|
||||||
|
const adj2 = getRandomElement(WORDS.adjectives);
|
||||||
|
const noun = getRandomElement(WORDS.nouns);
|
||||||
|
return `${adj1}${adj2}${noun}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
doubleNoun: (): string => {
|
||||||
|
const noun1 = getRandomElement(WORDS.nouns);
|
||||||
|
const noun2 = getRandomElement(WORDS.nouns);
|
||||||
|
const number = Math.floor(Math.random() * 100);
|
||||||
|
return `${noun1}${number}${noun2}`;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function generateUsername(): string {
|
||||||
|
const patterns = Object.values(usernamePatterns);
|
||||||
|
const selectedPattern = getRandomElement(patterns);
|
||||||
|
return selectedPattern();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateUniqueUsername(
|
||||||
|
checkExists: (username: string) => Promise<boolean>
|
||||||
|
): Promise<string> {
|
||||||
|
const MAX_ATTEMPTS = 10;
|
||||||
|
let attempts = 0;
|
||||||
|
let username = generateUsername();
|
||||||
|
|
||||||
|
while (await checkExists(username) && attempts < MAX_ATTEMPTS) {
|
||||||
|
username = generateUsername();
|
||||||
|
attempts++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attempts >= MAX_ATTEMPTS) {
|
||||||
|
// Add a large random number to ensure uniqueness
|
||||||
|
username = generateUsername() + Math.floor(Math.random() * 1000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
return username;
|
||||||
|
}
|
@ -5,6 +5,12 @@ const nextConfig = {
|
|||||||
{
|
{
|
||||||
hostname: "cdn.simpleicons.org",
|
hostname: "cdn.simpleicons.org",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
hostname: "img.clerk.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
hostname: "images.clerk.dev",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
1987
frontend/package-lock.json
generated
1987
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -27,9 +27,11 @@
|
|||||||
"@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.0.2",
|
||||||
"@radix-ui/react-popover": "^1.1.1",
|
"@radix-ui/react-popover": "^1.1.1",
|
||||||
|
"@radix-ui/react-progress": "^1.1.0",
|
||||||
"@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.0.2",
|
||||||
"@radix-ui/react-switch": "^1.0.3",
|
"@radix-ui/react-switch": "^1.0.3",
|
||||||
|
"@radix-ui/react-tooltip": "^1.1.3",
|
||||||
"@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",
|
||||||
@ -57,6 +59,7 @@
|
|||||||
"react-resizable-panels": "^2.0.16",
|
"react-resizable-panels": "^2.0.16",
|
||||||
"react-syntax-highlighter": "^15.5.0",
|
"react-syntax-highlighter": "^15.5.0",
|
||||||
"remark-gfm": "^4.0.0",
|
"remark-gfm": "^4.0.0",
|
||||||
|
"shadcn": "^2.1.6",
|
||||||
"socket.io-client": "^4.7.5",
|
"socket.io-client": "^4.7.5",
|
||||||
"sonner": "^1.4.41",
|
"sonner": "^1.4.41",
|
||||||
"tailwind-merge": "^2.3.0",
|
"tailwind-merge": "^2.3.0",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user