It's a place where Tabletop Roleplaying Game (TTRPG) players can customize their character sheet with a Drag and Drop interface.
Most services are either locked behind a paywall for customization options or free but static and uninspired.
Players of any tabletop system looking to customize their character sheet their way
- Drag and Drop components - Put what you want, where you want, however big you want
- Snap to Grid - Keeping things nice and clean
- Interactive elements - Add dice rolling, text, and images to each element
- React
- dndkit/react or react-draggable
- Node
- Express
- MongoDB (for character sheet structure)
- Mongoose.js (to use MongoDB in Express Server)
- JWT/OAuth/SSO (for Authentication)
- None (so far, but maybe in the future)
- Home Page - The Editor (The aim is that it should be free)
- Character Select Screen
- Login Page
character_sheets.character_sheet JSON structure
[
{
"x": "int: x-coordinate of draggable element.",
"y": "int: y-coordinate of draggable element.",
"width": "int: width of draggable element.",
"height": "int: height of draggable element.",
"type": "varchar: type of the draggable element.",
"name": "varchar: name of the draggable element",
"text": "varchar: text typed into the draggable element",
"diceNum": "int: number of dice to be rolled (if used)",
"diceType": "int: type of dice to be rolled (if used)",
"modifier": "int: value added to dice roll (if used)",
"img_link": "varchar: link to the image (if used)"
},
{
"x": "0",
"y": "0",
"width": "10",
"height": "2",
"type": "text",
"name": "character name",
"text": "Glorius Character Name"
},
{
"x": "0",
"y": "2",
"width": "3",
"height": "3",
"type": "image",
"name": "character portrait",
"img_link": "http://www.link.to/awesome/character/image"
},
{
"x": "3",
"y": "2",
"width": "1",
"height": "3",
"type": "dice",
"name": "str",
"text": "+4",
"diceNum": "1",
"diceType": "20",
"modifier": "4",
"img_link": "http://www.link.to/some/icon/for/strength"
}
]
Sheets
GET /api/v1/sheets
- Gets all sheet data
Response Body Exampe:
[
{
"id": 0,
"userID": 0,
"sheet_name": "Character 1"
},
{
"id": 1,
"userID": 0,
"sheet_name": "Character 2"
},
{
"id": 2,
"userID": 1,
"sheet_name": "Hello World!"
}
]
POST /api/v1/sheets
- Create a new sheet
Request body example:
{
"userID": 2,
"sheet_name": "",
"character_sheet": [
{
"x": "0",
"y": "0",
"width": "10",
"height": "2",
"type": "text",
"name": "character name",
"text": "Glorius Character Name"
},
{
"x": "0",
"y": "2",
"width": "3",
"height": "3",
"type": "image",
"name": "character portrait",
"img_link": "http://www.link.to/awesome/character/image"
},
{
"x": "3",
"y": "2",
"width": "1",
"height": "3",
"type": "dice",
"name": "str",
"text": "+4",
"diceNum": "1",
"diceType": "20",
"modifier": "4",
"img_link": "http://www.link.to/some/icon/for/strength"
}
]
}
Response Body Exampe:
{
"message": "Character sheet saved successfully.",
"savedItem": {
"id": 3,
"userID": 2,
"sheet_name": "test2's character",
"character_sheet": [
{
"x": "0",
"y": "0",
"width": "10",
"height": "2",
"type": "text",
"name": "character name",
"text": "Glorius Character Name"
},
{
"x": "0",
"y": "2",
"width": "3",
"height": "3",
"type": "image",
"name": "character portrait",
"img_link": "http://www.link.to/awesome/character/image"
},
{
"x": "3",
"y": "2",
"width": "1",
"height": "3",
"type": "dice",
"name": "str",
"text": "+4",
"diceNum": "1",
"diceType": "20",
"modifier": "4",
"img_link": "http://www.link.to/some/icon/for/strength"
}
]
}
}
GET /api/v1/sheets/:id
- Gets sheet data for a sheet id
Response Body Exampe:
[
{
"x": "0",
"y": "0",
"width": "10",
"height": "2",
"type": "text",
"name": "character name",
"text": "Glorius Character Name"
},
{
"x": "0",
"y": "2",
"width": "3",
"height": "3",
"type": "image",
"name": "character portrait",
"img_link": "http://www.link.to/awesome/character/image"
},
{
"x": "3",
"y": "2",
"width": "1",
"height": "3",
"type": "dice",
"name": "str",
"text": "+4",
"diceNum": "1",
"diceType": "20",
"modifier": "4",
"img_link": "http://www.link.to/some/icon/for/strength"
}
]
PUT /api/v1/sheets/:id
- Update sheet data for a sheet id
Response Body Exampe:
[
{
"x": "0",
"y": "0",
"width": "10",
"height": "2",
"type": "text",
"name": "character name",
"text": "Glorius Character Name"
},
{
"x": "0",
"y": "2",
"width": "3",
"height": "3",
"type": "image",
"name": "character portrait",
"img_link": "http://www.link.to/awesome/character/image"
},
{
"x": "3",
"y": "2",
"width": "1",
"height": "3",
"type": "dice",
"name": "str",
"text": "+4",
"diceNum": "1",
"diceType": "20",
"modifier": "4",
"img_link": "http://www.link.to/some/icon/for/strength"
},
{
"x": "3",
"y": "2",
"width": "1",
"height": "3",
"type": "dice",
"name": "con",
"text": "+5",
"diceNum": "1",
"diceType": "20",
"modifier": "5",
"img_link": "http://www.link.to/some/icon/for/constitution"
}
]
Response Body Exampe:
{
"message": "test2's character sheet saved successfully.",
"savedItem": {
[
{
"x": "0",
"y": "0",
"width": "10",
"height": "2",
"type": "text",
"name": "character name",
"text": "Glorius Character Name"
},
{
"x": "0",
"y": "2",
"width": "3",
"height": "3",
"type": "image",
"name": "character portrait",
"img_link": "http://www.link.to/awesome/character/image"
},
{
"x": "3",
"y": "2",
"width": "1",
"height": "3",
"type": "dice",
"name": "str",
"text": "+4",
"diceNum": "1",
"diceType": "20",
"modifier": "4",
"img_link": "http://www.link.to/some/icon/for/strength"
},
{
"x": "3",
"y": "2",
"width": "1",
"height": "3",
"type": "dice",
"name": "con",
"text": "+5",
"diceNum": "1",
"diceType": "20",
"modifier": "5",
"img_link": "http://www.link.to/some/icon/for/constitution"
}
]
}
}
DELETE /api/v1/sheets/:id
- Delete a sheet
Response Body Exampe:
{
"message": "test2 character sheet deleted successfully."
}
GET /api/v1/sheets/user/:id
- Gets all sheet data for a particular user
Response Body Exampe:
[
{
"id": 0,
"userID": 0,
"sheet_name": "Character 1"
},
{
"id": 1,
"userID": 0,
"sheet_name": "Character 2"
}
]
Users
POST /api/v1/user/
- Create a new user
Request body example:
{
"username": "test2",
"email": "test2@example.com",
"password": "securepassword"
}
Response Body Exampe:
{
"message": "User test2 created successfully.",
"user": {
"id": "2",
"username": "test2",
"email": "test2@example.com"
}
}
GET /api/v1/user/:id
- Gets info for a particular user
Response Body Exampe:
{
"id": 0,
"username": "test1",
"email": "test1@example.com"
}
PUT /api/v1/user/:id
- Edit a user
Request body example:
{
"username": "test2",
"email": "test2@change.com",
"password": "securepassword"
}
Response Body Exampe:
{
"message": "User test2 edited successfully.",
"user": {
"id": "2",
"username": "test2",
"email": "test2@change.com"
}
}
DELETE /api/v1/user/:id
- Delete a user
Response Body Exampe:
{
"message": "User test2 deleted successfully."
}
POST /api/users/login
- Authenticate a user and return a token
Request body example:
{
"email": "test2@example.com",
"password": "securepassword"
}
Response body example:
{
"message": "Login successful.",
"token": "jwt-token-here",
"user": {
"id": "2",
"username": "test2",
"email": "test2@example.com"
}
}
- Testing Phase
- Experiment with MongoDB
- Test react-draggable vs dnd-kit
- Front End
- Initialize repos
- Create folder structure
- Set up HTML and JSX structure with hard-coded values
- Work on SASS
- Order:
- Home Page
- Character Sheet Page
- Database
- Set up Database
- Create collections
- Back End
- Initialize repos
- Create folder structure
- Set up routes
- Initialize migrations
- Create seed-data
- Initialize seed
- Order:
- Sheets
GET
POST
DELETE
PUT
- Sheets
- Back End
- Connect to Database
- Front End
- Connect to API
- Test full product
- Demo MVP
- Front End
- Login Page
- Back End:
- Users
GET
POST
DELETE
PUT
- JWT/OAuth/SSO Integration
- Users
- Deployment
- Connect to Netlify