diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 00000000..97175cda
Binary files /dev/null and b/.DS_Store differ
diff --git a/.gitignore b/.gitignore
index 1b5c3a24..f9efe28f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -182,4 +182,5 @@ fabric.properties
.idea/caches/build_file_checksums.ser
# idea folder, uncomment if you don't need it
-.idea
\ No newline at end of file
+.idea
+random/
\ No newline at end of file
diff --git a/Procfile b/Procfile
new file mode 100644
index 00000000..ca6e941c
--- /dev/null
+++ b/Procfile
@@ -0,0 +1 @@
+web: gunicorn app:app
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..9789b431
--- /dev/null
+++ b/README.md
@@ -0,0 +1,51 @@
+# ChainedSimple
+
+## Inspiration
+After working at some of the largest Canadian banks, we identified a common problem: the traditional process of verifying financial statements for KYC compliance is slow, inefficient, and insecure. Common challenges include:
+- Data tampering
+- Lengthy verification processes
+- Interbank trust barriers
+
+These issues lead to a slow and tedious process for customer onboarding. Additionally, when financial statements are required for business activities such as credit scoring, verifying their accuracy can raise concerns, further slowing down transactions.
+
+## What It Does
+ChainedSimple is a solution that:
+- Allows users to upload financial documents to the Blockchain.
+- Enables access to these documents via a Wallet Address to share securely with others.
+- Utilizes AI to analyze user data.
+
+This approach ensures data integrity while keeping it more secure.
+
+## How We Built It
+- **Backend**: Built using Flask to handle API calls and support the application. We integrated the Verbwire API to mint NFTs and attach them to customer wallet addresses.
+- **AI Integration**: The Cohere API was used for analyzing data.
+- **Frontend**: Developed with standard HTML, CSS, and JavaScript to provide a user-friendly interface.
+
+## Challenges We Ran Into
+1. **Learning Curve**: None of us had prior experience with blockchain, and this was our first time implementing anything involving NFTs.
+2. **Flask Challenges**: Understanding variable scope in Flask when passing data between pages or functions caused blockers during development.
+
+## Accomplishments That We're Proud Of
+We are extremely proud of:
+- Successfully implementing the Verbwire API to mint NFTs with metadata and attach them to wallet addresses.
+- Analyzing financial data using AI.
+- Celebrating each milestone as we inched closer to completing our project.
+
+## What We Learned
+1. **Blockchain Fundamentals**: We gained a deep understanding of how the blockchain works and its benefits.
+2. **Teamwork Lessons**: We realized that adding more hands to the wheel doesn’t always make the car drive faster—it just makes it harder to steer.
+
+## What's Next for ChainedSimple
+Our future plans include:
+- Enhancing the UI for a smoother user experience.
+- Adding workflows that allow users to share their NFT with others, such as through a QR code. This would enable users to share information like credit scores quickly and conveniently.
+
+## Built With
+- [Cohere](https://cohere.ai/)
+- CSS
+- [Figma](https://www.figma.com/)
+- Flask
+- HTML
+- JavaScript
+- Python
+- [Verbwire](https://www.verbwire.com/)
diff --git a/app.py b/app.py
index 6ad4f3ff..5505f6dd 100644
--- a/app.py
+++ b/app.py
@@ -1,39 +1,100 @@
from pprint import pprint
-from flask import Flask, request, jsonify, redirect
-from flask_cors import CORS
+
+import requests
+from flask import Flask, render_template, request, redirect, url_for, jsonify, session
+from utils.verbwire import get_wallet_nfts, update_nft_metadata
import os
import json
-import time
-from datetime import datetime
+import cohere
-# Import Verbwire helper functions
+# Import the refactored Verbwire helper functions
from utils.verbwire import (
mint_nft_from_metadata_url,
get_wallet_nfts,
check_transaction_status,
update_nft_metadata,
upload_file_to_ipfs,
- get_nft_details # <-- We import this to fetch the NFT's metadata
+ store_file_as_metadata,
)
app = Flask(__name__)
-CORS(app)
-@app.route('/login', methods=['POST'])
+# MetaMask Login Page
+@app.route("/")
def login():
+
+ return render_template("login.html")
+
+# Dashboard Page
+@app.route("/dashboard", methods=["GET","POST"])
+def dashboard():
+ try:
+ if request.method == "POST":
+ wallet_address = request.form.get("wallet_address")
+ else:
+ wallet_address = request.args.get("wallet_address") # For GET requests
+
+ if not wallet_address:
+ return render_template("login.html", error="Wallet address is required.")
+
+ # Rest of the code remains the same
+ nfts_response = get_wallet_nfts(wallet_address)
+ if "error" in nfts_response:
+ return render_template("login.html", error=nfts_response["error"])
+
+ nfts = nfts_response.get("nfts", [])
+ return render_template("dashboard.html", wallet_address=wallet_address, nfts=nfts)
+ except Exception as e:
+ print(e)
+ return render_template("login.html", error="An error occurred.")
+
+
+
+
+# Homepage
+@app.route("/home", methods=["POST"])
+def home():
+ wallet_address = request.form.get("wallet_address")
+ return render_template("homepage.html", wallet_address=wallet_address)
+
+@app.route('/register', methods=['POST'])
+def register():
+ data = request.get_json()
+ wallet_address = data.get("wallet_address")
+ metadata_url = data.get("metadata_url")
+ chain = data.get("chain", "sepolia")
+
+ if not wallet_address or not metadata_url:
+ print("wallet_address or metadata_url not found")
+ return jsonify({"error": "wallet_address and metadata_url are required"}), 400
+
+ resp = mint_nft_from_metadata_url(metadata_url, wallet_address, chain=chain)
+ #pprint(resp)
+ return jsonify(resp), 200
+
+
+@app.route('/auth', methods=['POST'])
+def auth_user():
"""
- User provides a wallet address, we fetch all NFTs they own.
- Expects JSON: { "wallet_address": "
" }
+ Checks whether a given wallet has any NFTs (via Verbwire's /nft/data/owned).
+ Expects JSON:
+ {
+ "wallet_address": ""
+ }
+ Returns the list of NFTs if any are found.
"""
data = request.get_json()
wallet_address = data.get("wallet_address")
if not wallet_address:
return jsonify({"error": "wallet_address is required"}), 400
+ # Default to chain="sepolia" and tokenType="nft721"
nfts_response = get_wallet_nfts(wallet_address)
- pprint(nfts_response)
+ #pprint(nfts_response)
+ # If there's an error or the "nfts" list is empty, handle accordingly
if "error" in nfts_response:
+ # Means we got some error from Verbwire
return jsonify({
"authenticated": False,
"error": nfts_response["error"]
@@ -41,11 +102,7 @@ def login():
nfts_list = nfts_response.get("nfts", [])
if not nfts_list:
- return jsonify({
- "authenticated": False,
- "error": "No NFTs found for this wallet address.",
- "nfts": []
- }), 200
+ return jsonify({"authenticated": False, "error": "No NFTs found for this wallet address."}), 404
return jsonify({
"authenticated": True,
@@ -54,185 +111,276 @@ def login():
}), 200
-@app.route('/mint_file_nft', methods=['POST'])
-def mint_file_nft():
+@app.route('/check_status', methods=['POST'])
+def check_status():
"""
- The user uploads a file -> we upload to IPFS -> we create minimal metadata referencing that file
- -> we also upload that metadata to IPFS -> we mint an NFT from that metadata to the user's wallet.
-
- Form-data:
- wallet_address=...
- chain=sepolia (optional)
- file=@someLocalFile
+ Check the status of a minting transaction by ID.
+ Expects JSON:
+ {
+ "transaction_id": ""
+ }
"""
- wallet_address = request.form.get("wallet_address")
- chain = request.form.get("chain", "sepolia")
+ data = request.get_json()
+ transaction_id = data.get("transaction_id")
- if not wallet_address:
- return jsonify({"error": "wallet_address is required"}), 400
+ if not transaction_id:
+ return jsonify({"error": "transaction_id is required"}), 400
+
+ resp = check_transaction_status(transaction_id)
+ # pprint(resp)
+ return jsonify(resp), 200
+
+
+# @app.route("/update_metadata", methods=["POST"])
+# def update_metadata():
+# """
+# Updates the metadata of an existing NFT.
+# Handles both JSON and form data.
+# """
+
+# # Check if the data is coming as JSON or form data
+# if request.is_json:
+# # Handle JSON data
+# data = request.get_json()
+# contract_address = data.get("contract_address")
+# token_id = data.get("token_id")
+# new_token_uri = data.get("new_token_uri")
+# chain = data.get("chain", "sepolia") # Default to "sepolia"
+# else:
+# # Handle form data
+# contract_address = request.form.get("contract_address")
+# token_id = request.form.get("token_id")
+# new_token_uri = request.form.get("new_token_uri")
+# chain = request.form.get("chain", "sepolia") # Default to "sepolia"
+
+# # Validate required fields
+# if not all([contract_address, token_id, new_token_uri]):
+# return jsonify({"error": "contract_address, token_id, and new_token_uri are required"}), 400
+
+# # Call the utility function to update NFT metadata
+# update_response = update_nft_metadata(
+# contract_address=contract_address,
+# token_id=token_id,
+# new_token_uri=new_token_uri,
+# chain=chain
+# )
+
+# # Handle response from the utility function
+# if "error" in update_response:
+# return jsonify({"error": update_response["error"]}), 400
+
+# # Redirect to the dashboard if using form data or return JSON response
+# if not request.is_json:
+# return redirect(url_for("dashboard"))
+
+# return jsonify(update_response), 200
+
+
+
+@app.route('/upload_file', methods=['POST'])
+def upload_file():
if 'file' not in request.files:
- return jsonify({"error": "No file in the request"}), 400
+ return jsonify({"error": "No file part in the request"}), 400
file = request.files['file']
if file.filename == '':
return jsonify({"error": "No selected file"}), 400
- # 1. Upload the raw file to IPFS
+ wallet_address = request.form.get("wallet_address")
+ if not wallet_address:
+ return jsonify({"error": "No wallet address provided"}), 400
+
file_path = f"/tmp/{file.filename}"
file.save(file_path)
- try:
- ipfs_resp = upload_file_to_ipfs(file_path)
- finally:
- os.remove(file_path)
+ name = request.form.get("name", "Bank statement")
+ description = request.form.get("description", "Statement form")
- if "error" in ipfs_resp:
+ try:
+ # Store file metadata
+ resp = store_file_as_metadata(file_path, name=name, description=description)
+ if "error" in resp:
+ return jsonify({"error": resp["error"]}), 400
+
+ # Get IPFS URL and mint NFT
+ ipfs_url = resp.get("ipfs_storage", {}).get("ipfs_url")
+ print("IPFS URL", ipfs_url)
+ if not ipfs_url:
+ return jsonify({"error": "Failed to get IPFS URL"}), 400
+ print("Wallet address", wallet_address)
+ print("IPFS URL", ipfs_url)
+ response = mint_nft_from_metadata_url(ipfs_url, wallet_address, chain="sepolia")
+ if "error" in response:
+ return jsonify({"error": response["error"]}), 400
+
+ # Return JSON response instead of redirect
return jsonify({
- "error": "Failed to upload file to IPFS",
- "details": ipfs_resp
- }), 400
+ "success": True,
+ "wallet_address": wallet_address,
+ "transaction": response
+ }), 200
+
+ except Exception as e:
+ return jsonify({"error": str(e)}), 500
+ finally:
+ # Clean up temporary file
+ if os.path.exists(file_path):
+ os.remove(file_path)
- ipfs_url = ipfs_resp.get("ipfs_storage", {}).get("ipfs_url")
- if not ipfs_url:
- return jsonify({
- "error": "Could not parse IPFS URL from response",
- "response": ipfs_resp
- }), 400
- # 2. Create a minimal metadata JSON that references the file
- metadata = {
- "name": "My Uploaded File",
- "description": "NFT representing a user-uploaded file",
- "image": ipfs_url,
- "date_created": datetime.utcnow().isoformat()
- }
- timestamp_str = str(int(time.time()))
- temp_metadata_file = f"/tmp/metadata_{timestamp_str}.json"
- with open(temp_metadata_file, 'w') as f:
- json.dump(metadata, f)
+@app.route('/upload', methods=['GET', 'POST'])
+def upload_page():
+ """
+ Handles the upload page and processes file uploads.
+ """
+ #print("hello")
+ if request.method == 'POST':
+ if 'file' not in request.files:
+ return render_template('upload.html', error="No file part in the request.")
+
+ file = request.files['file']
+ name = "BAnk statement"
+ description = "statemnt form"
+
+ if file.filename == '':
+ return render_template('upload.html', error="No selected file.")
+
+ file_path = f"/tmp/{file.filename}"
+ file.save(file_path)
+
+ try:
+ response = store_file_as_metadata(file_path, name=name, description=description)
+ # print("hello")
+ # Add success data to pass to the template
+ return render_template('upload.html', success=response)
+ finally:
+ os.remove(file_path)
+
+ return render_template('upload.html')
+
+@app.route('/view_nft_image', methods=['GET'])
+def view_nft_image():
+ """
+ Calls Verbwire's getNFTDetails endpoint to retrieve the NFT metadata,
+ then redirects to the 'image' URL in the metadata.
- # 3. Upload that JSON to IPFS
- try:
- meta_ipfs_resp = upload_file_to_ipfs(temp_metadata_file)
- finally:
- os.remove(temp_metadata_file)
+ Query Parameters:
+ - contract_address: NFT contract address
+ - token_id: NFT token ID
+ - chain: Blockchain network (default: "sepolia")
+ """
+ contract_address = request.args.get("contract_address")
+ token_id = request.args.get("token_id")
+ chain = request.args.get("chain", "sepolia")
- if "error" in meta_ipfs_resp:
- return jsonify({
- "error": "Failed to upload metadata JSON to IPFS",
- "details": meta_ipfs_resp
- }), 400
+ if not contract_address or not token_id:
+ return jsonify({"error": "Missing contract_address or token_id"}), 400
- metadata_ipfs_url = meta_ipfs_resp.get("ipfs_storage", {}).get("ipfs_url")
- if not metadata_ipfs_url:
- return jsonify({
- "error": "Could not parse IPFS URL for metadata",
- "response": meta_ipfs_resp
- }), 400
+ # Step 1: Fetch NFT details using the Verbwire helper
+ from utils.verbwire import get_nft_details
+ details = get_nft_details(contract_address, token_id, chain=chain, populate_metadata=True)
+ ipfs_uri = details.get('nft_details').get("tokenURI")
+ ipfs = requests.get(ipfs_uri)
- # 4. Mint NFT from the metadata IPFS URL
- mint_resp = mint_nft_from_metadata_url(metadata_ipfs_url, wallet_address, chain=chain)
+ metadata = details.get("nft_details").get("metadata")
+ if "error" in details:
+ return jsonify({"error": details["error"]}), 400
- return jsonify({
- "minted": True,
- "wallet_address": wallet_address,
- "metadata_ipfs_url": metadata_ipfs_url,
- "mint_response": mint_resp
- }), 200
+ # Step 2: Redirect to the image URL
+ return jsonify(metadata)
-@app.route('/check_status', methods=['POST'])
-def check_status():
+def get_view_nft_data(token_id, contract_address, chain="sepolia"):
"""
- Check the status of a minting transaction by ID.
- Expects JSON: { "transaction_id": "...someTxID..." }
+ Calls Verbwire's getNFTDetails endpoint to retrieve the NFT metadata,
+ then redirects to the 'image' URL in the metadata.
+
+ Query Parameters:
+ - contract_address: NFT contract address
+ - token_id: NFT token ID
+ - chain: Blockchain network (default: "sepolia")
"""
- data = request.get_json()
- transaction_id = data.get("transaction_id")
- if not transaction_id:
- return jsonify({"error": "transaction_id is required"}), 400
- resp = check_transaction_status(transaction_id)
- pprint(resp)
- return jsonify(resp), 200
+ if not contract_address or not token_id:
+ return jsonify({"error": "Missing contract_address or token_id"}), 400
+ # Step 1: Fetch NFT details using the Verbwire helper
+ from utils.verbwire import get_nft_details
+ details = get_nft_details(contract_address, token_id, chain=chain, populate_metadata=True)
+ ipfs_uri = details.get('nft_details').get("tokenURI")
+ ipfs = requests.get(ipfs_uri)
-@app.route('/update_metadata', methods=['POST'])
-def update_metadata_endpoint():
- """
- Directly updates an NFT's on-chain metadata to a new URI.
+ metadata = details.get("nft_details").get("metadata")
+ if "error" in details:
+ return jsonify({"error": details["error"]}), 400
+
+ # Step 2: Redirect to the image URL
+ return metadata
+@app.route('/process_nft_with_llm', methods=['POST'])
+def process_nft_with_llm():
+ """
+ Processes NFT metadata using Cohere's LLM.
Expects JSON:
{
- "contract_address": "...",
- "token_id": "...",
- "new_token_uri": "...",
- "chain": "sepolia" (optional)
+ "contract_address": "",
+ "token_id": "",
+ "chain": ""
}
+ Retrieves metadata using /view_nft_image and processes it with Cohere's LLM.
"""
- data = request.get_json()
- contract_address = data.get("contract_address")
- token_id = data.get("token_id")
- new_token_uri = data.get("new_token_uri")
- chain = data.get("chain", "sepolia")
- if not contract_address or not token_id or not new_token_uri:
- return jsonify({
- "error": "contract_address, token_id, and new_token_uri are required"
- }), 400
+ data = request.get_json()
+ contract_address = data.get('contract_address')
+ token_id = data.get('token_id')
+ print (contract_address,token_id)
+ chain = data.get("chain", "sepolia") # Default to "sepolia"
- resp = update_nft_metadata(
- contract_address=contract_address,
- token_id=token_id,
- new_token_uri=new_token_uri,
- chain=chain
- )
- pprint(resp)
- return jsonify(resp), 200
+ if not contract_address or not token_id:
+ return jsonify({"error": "Missing contract_address or token_id"}), 400
+ try:
+ # Step 1: Retrieve metadata from /view_nft_image
+ view_nft_response = get_view_nft_data(token_id, contract_address, chain=chain)
-@app.route('/view_nft_image', methods=['POST'])
-def view_nft_image():
- """
- Calls Verbwire's getNFTDetails endpoint to retrieve the NFT metadata,
- then redirects to the 'image' URL in the metadata.
+
+ pprint(str(view_nft_response))
- Expects JSON:
- {
- "contract_address": "",
- "token_id": "",
- "chain": "sepolia" (optional)
- }
- If found, we do a 302 redirect to the 'image' link from the metadata.
- """
- data = request.get_json()
- contract_address = data.get("contract_address")
- token_id = data.get("token_id")
- chain = data.get("chain", "sepolia")
+ # Step 2: Validate metadata is JSON
+ #if not isinstance(metadata, dict):
+ # return jsonify({"error": "Metadata is not in JSON format."}), 400
- if not contract_address or not token_id:
- return jsonify({"error": "contract_address and token_id are required"}), 400
- # Step 1: Get NFT details from Verbwire
- from utils.verbwire import get_nft_details
- details = get_nft_details(contract_address, token_id, chain=chain, populate_metadata=True)
- if "error" in details:
- return jsonify({"error": details["error"]}), 400
+ # Step 3: Process metadata with Cohere
+ co = cohere.Client(os.getenv("COHERE_API_KEY"))
+ prompt = (
+ f"{str(view_nft_response)} Here is the JSON for a person's financial data. "
+ "Provide short feedback to the user about their finances."
+ )
- nft_details = details.get("nft_details", {})
- metadata = nft_details.get("metadata", {})
- image_url = metadata.get("image")
+ response = co.generate(
+ model="command-r-plus-08-2024",
+ prompt=prompt,
+ max_tokens=150,
+ temperature=0.7,
+ )
- if not image_url:
+ feedback = response.generations[0].text.strip()
+ print(feedback)
+ # Step 4: Return processed feedback
return jsonify({
- "error": "No image URL found in NFT metadata.",
- "metadata": metadata
- }), 400
+ "contract_address": contract_address,
+ "token_id": token_id,
+ "processed_text": feedback
+ }), 200
+
+ except Exception as e:
+ return jsonify({"error": str(e)}), 500
- # Step 2: Redirect the user to that image URL
- return redirect(image_url, code=302)
if __name__ == '__main__':
app.run(debug=True)
+
+
diff --git a/requirements.txt b/requirements.txt
index a9ef7a81..0fe8cb34 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,14 +1,36 @@
+annotated-types==0.7.0
+anyio==4.8.0
blinker==1.9.0
certifi==2024.12.14
charset-normalizer==3.4.1
click==8.1.8
+cohere==5.13.6
+exceptiongroup==1.2.2
+fastavro==1.10.0
+filelock==3.16.1
Flask==3.1.0
-Flask-Cors
+fsspec==2024.12.0
+h11==0.14.0
+httpcore==1.0.7
+httpx==0.28.1
+httpx-sse==0.4.0
+huggingface-hub==0.27.1
idna==3.10
itsdangerous==2.2.0
Jinja2==3.1.5
MarkupSafe==3.0.2
+packaging==24.2
+parameterized==0.9.0
+pydantic==2.10.5
+pydantic_core==2.27.2
python-dotenv==1.0.1
+PyYAML==6.0.2
requests==2.32.3
+sniffio==1.3.1
+tokenizers==0.21.0
+tqdm==4.67.1
+types-requests==2.32.0.20241016
+typing_extensions==4.12.2
urllib3==2.3.0
Werkzeug==3.1.3
+gunicorn
diff --git a/static/.DS_Store b/static/.DS_Store
new file mode 100644
index 00000000..c384eb60
Binary files /dev/null and b/static/.DS_Store differ
diff --git a/static/css/dashboard-styles.css b/static/css/dashboard-styles.css
new file mode 100644
index 00000000..396dd707
--- /dev/null
+++ b/static/css/dashboard-styles.css
@@ -0,0 +1,215 @@
+body {
+ font-family: Arial, sans-serif;
+ margin: 0;
+ padding: 0;
+ background-color: #f5f5f5;
+ color: #333;
+}
+
+.container {
+ max-width: 800px;
+ margin: 20px auto;
+ padding: 20px;
+ background-color: #fff;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+ border-radius: 8px;
+}
+
+h1 {
+ font-size: 2rem;
+ color: #2c3e50;
+ margin-bottom: 10px;
+ text-align: center;
+}
+
+h2 {
+ font-size: 1.5rem;
+ color: #34495e;
+ margin-top: 20px;
+ margin-bottom: 10px;
+}
+
+h3 {
+ font-size: 1.2rem;
+ color: #2c3e50;
+ margin-bottom: 5px;
+}
+
+p {
+ margin: 5px 0;
+ line-height: 1.5;
+}
+
+ul {
+ list-style-type: none;
+ padding: 0;
+}
+
+li {
+ margin-bottom: 20px;
+ padding: 15px;
+ border: 1px solid #ddd;
+ border-radius: 8px;
+ background-color: #f9f9f9;
+}
+
+/* Links */
+a {
+ color: #8b1d3f;
+ text-decoration: none;
+ font-weight: bold;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+/* Buttons */
+button {
+ background-color: #FFF;
+ color: black;
+ border: 1px solid #ddd;
+ border-radius: 8px;
+ padding: 10px 15px;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 1rem;
+}
+
+button:hover {
+ background-color: #8b1d3f;
+ color: white;
+}
+
+/* Upload Section */
+.upload-section {
+ margin-top: 30px;
+ padding: 20px;
+ border: 1px solid #ddd;
+ border-radius: 8px;
+ background-color: #f9f9f9;
+}
+
+label {
+ display: block;
+ margin-bottom: 5px;
+ font-weight: bold;
+}
+
+input[type="file"] {
+ display: block;
+ margin-bottom: 15px;
+}
+
+#upload-status {
+ margin-top: 10px;
+ font-style: italic;
+ color: #27ae60;
+}
+
+/* NFT Details */
+#nft-details {
+ display: none;
+ margin-top: 10px;
+ padding: 10px;
+ border: 1px solid #ddd;
+ border-radius: 8px;
+ background-color: #f9f9f9;
+}
+
+#nft-details img {
+ max-width: 100%;
+ border-radius: 4px;
+ margin-bottom: 10px;
+}
+
+/* Responsive Design */
+@media (max-width: 600px) {
+ .container {
+ padding: 15px;
+ }
+
+ h1, h2 {
+ text-align: left;
+ }
+}
+
+
+/* General container styling */
+.upload-section {
+ margin-top: 30px;
+ padding: 20px;
+ border: 1px solid #ddd;
+ border-radius: 8px;
+ background-color: #f9f9f9;
+ max-width: 800px; /* Match the width of the .container */
+ margin: 20px auto; /* Center the section within the container */
+}
+
+/* Header styling */
+.upload-section h2 {
+ color: #333;
+ font-size: 24px;
+ margin-bottom: 20px;
+ text-align: center;
+}
+
+/* Form styling */
+.upload-section form {
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+}
+
+/* Label styling */
+.upload-section label {
+ font-weight: bold;
+ color: #555;
+ margin-bottom: 5px;
+}
+
+/* Input field styling */
+.upload-section input[type="text"],
+.upload-section input[type="file"],
+.upload-section textarea {
+ width: 100%;
+ padding: 10px;
+ border: 1px solid #ccc;
+ border-radius: 5px;
+ font-size: 14px;
+}
+
+/* File input styling */
+.upload-section input[type="file"] {
+ padding: 5px;
+}
+
+/* Textarea styling */
+.upload-section textarea {
+ min-height: 100px;
+ resize: vertical;
+}
+
+/* Submit button styling */
+.upload-section button {
+ padding: 10px 15px;
+ background-color: #8b1d3f;
+ color: #fff;
+ border: none;
+ border-radius: 5px;
+ font-size: 16px;
+ cursor: pointer;
+ transition: background-color 0.3s;
+}
+
+.upload-section button:hover {
+ background-color: #8b1d3f;
+}
+
+/* Status message styling */
+.upload-section #upload-status {
+ margin-top: 10px;
+ font-size: 14px;
+ color: #777;
+ text-align: center;
+}
diff --git a/static/css/homepage-styles.css b/static/css/homepage-styles.css
new file mode 100644
index 00000000..7a79b442
--- /dev/null
+++ b/static/css/homepage-styles.css
@@ -0,0 +1,116 @@
+body{
+ padding: 0px;
+ margin: 0;
+ }
+.title {
+ color: #5C6AC4;
+}
+
+
+.top {
+width: full;
+height: 80px;
+background: #8B1D40;
+display: flex;
+justify-content: flex-end;
+align-items: center;
+}
+
+.navButton {
+width: 135px;
+height: 20px;
+color: #FFF;
+font-family: Arial;
+font-size: 20px;
+font-style: normal;
+font-weight: 400;
+line-height: normal;
+}
+
+.left {
+ display: flex; /* Align buttons in a row */
+ gap: 20px; /* Optional: space between buttons */
+}
+
+.logo {
+margin-left: 10px;
+}
+
+
+
+.leftButton {
+ width: 150px;
+ height: 80px;
+ margin-left: 10px;
+ margin-bottom: 20px;
+ border-radius: 10px;
+ background: #F2F3F2;
+ display: flex;
+ justify-content: center; /* Center the text horizontally */
+ align-items: center; /* Center the text vertically */
+ color: black; /* Text color */
+ font-size: 18px; /* Text size */
+}
+
+.homeButtons {
+width: 150px;
+height: 80px;
+margin: 10px;
+border-radius: 10px;
+border: 1px solid #8A8282;
+background: #FFFFFF;
+display: flex;
+justify-content: center; /* Center the text horizontally */
+align-items: center; /* Center the text vertically */
+color: Black; /* Text color */
+font-size: 18px; /* Text size */
+}
+
+
+.main {
+display: flex; /* Create a flex container */
+
+gap: 20px; /* Optional: space between sections */
+}
+
+.mainLeft {
+display: flex;
+flex-direction: column; /* Arrange buttons vertically */
+gap: 10px; /* Optional: space between buttons */
+width: 200px; /* Adjust the width as needed */
+}
+
+.mainMiddle {
+flex: 1; /* Middle section takes up remaining space */
+background-color: #f0f0f0; /* Optional: background color for middle section */
+}
+
+.mainRight {
+width: 200px; /* Adjust the width as needed */
+background-color: #e0e0e0; /* Optional: background color for right section */
+}
+
+.mainLeftButton {
+width: 150px;
+height: 80px;
+border-radius: 10px;
+margin: 10px;
+background: #ffffff;
+display: flex;
+justify-content: center; /* Center the text horizontally */
+align-items: center; /* Center the text vertically */
+color: black; /* Text color */
+font-size: 18px; /* Text size */
+border: 2px solid #C41F3E; /* Optional: add a border around the buttons */
+}
+
+
+.mainLeftButton:hover {
+ background: #C41F3E; /* Change background to red on hover */
+ color: white; /* Optional: Change text color for better contrast */
+}
+
+
+.main-img {
+ width: 100%;
+}
\ No newline at end of file
diff --git a/static/css/styles.css b/static/css/styles.css
new file mode 100644
index 00000000..e69de29b
diff --git a/static/js/script.js b/static/js/script.js
new file mode 100644
index 00000000..e69de29b
diff --git a/templates/dashboard.html b/templates/dashboard.html
new file mode 100644
index 00000000..a15e57b8
--- /dev/null
+++ b/templates/dashboard.html
@@ -0,0 +1,160 @@
+
+
+
+
+
+ Dashboard
+
+
+
+