From ac0064bfa9fd9be419a7bc7ebdb73c16a18af79e Mon Sep 17 00:00:00 2001
From: mx00 <2784107+mx00@users.noreply.github.com>
Date: Fri, 15 May 2026 23:00:36 -0700
Subject: [PATCH 1/6] Display entry and exit prices on Trades tab in the trade
details
---
.gitignore | 1 +
src/views/Daily.vue | 28 ++++++++++++++++++++++++++++
2 files changed, 29 insertions(+)
diff --git a/.gitignore b/.gitignore
index 9f69cbb8..b28ec4c9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@ dist
.vscode
logs
.idea/
+docker-compose-local-MINE.yml
diff --git a/src/views/Daily.vue b/src/views/Daily.vue
index a68afab0..525d3fa2 100644
--- a/src/views/Daily.vue
+++ b/src/views/Daily.vue
@@ -948,6 +948,9 @@ function getOHLC(date, symbol, type) {
Position |
Entry |
+ Entry Price |
+ Exit |
+ Exit Price |
P&L/Sec
@@ -1002,6 +1005,31 @@ function getOHLC(date, symbol, type) {
v-bind:data-bs-title="'Swing trade from ' + useDateCalFormat(trade.entryTime)">
+
+ | {{
+ (trade.entryPrice).toFixed(5)
+ }}{{
+ useTwoDecCurrencyFormat(trade.entryPrice)
+ }}
+ |
+
+
+ {{ useTimeFormat(trade.exitTime) }} |
+
+
+ {{
+ (trade.exitPrice).toFixed(5)
+ }}{{
+ useTwoDecCurrencyFormat(trade.exitPrice)
+ }} |
+
Date: Mon, 18 May 2026 21:36:49 -0700
Subject: [PATCH 2/6] Fix manual screenshot tag fallback
---
src/utils/daily.js | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/utils/daily.js b/src/utils/daily.js
index 70676857..eb23685f 100644
--- a/src/utils/daily.js
+++ b/src/utils/daily.js
@@ -542,7 +542,7 @@ export const useUpdateTags = async () => {
query.equalTo("tradeId", tradeTagsId.value)
} else {
//it's the case when changing daily tags, tradeTagsId.value is null (see useTradeTagsChange)
- query.equalTo("tradeId", tradeTagsDateUnix.value.toString())
+ query.equalTo("tradeId", (tradeTagsDateUnix.value || screenshot.dateUnix || screenshot.dateUnixDay || screenshot.name || Date.now()).toString())
}
}
const results = await query.first();
@@ -578,12 +578,12 @@ export const useUpdateTags = async () => {
}
else {
if (tradeTagsId.value) {
- object.set("dateUnix", tradeTagsDateUnix.value)
+ object.set("dateUnix", tradeTagsDateUnix.value || screenshot.dateUnix || screenshot.dateUnixDay || Date.now())
object.set("tradeId", tradeTagsId.value)
} else {
//it's the case when changing daily tags, tradeTagsId.value is null
- object.set("dateUnix", tradeTagsDateUnix.value)
- object.set("tradeId", tradeTagsDateUnix.value.toString())
+ object.set("dateUnix", tradeTagsDateUnix.value || screenshot.dateUnix || screenshot.dateUnixDay || Date.now())
+ object.set("tradeId", (tradeTagsDateUnix.value || screenshot.dateUnix || screenshot.dateUnixDay || screenshot.name || Date.now()).toString())
}
}
From ab50cf5358045d87011b3f5137e36cd7d1f99c6e Mon Sep 17 00:00:00 2001
From: mx00 <2784107+mx00@users.noreply.github.com>
Date: Mon, 18 May 2026 23:03:44 -0700
Subject: [PATCH 3/6] Fix manual chart screenshot upload linking
---
src/utils/screenshots.js | 5 +++--
src/views/Daily.vue | 2 +-
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/utils/screenshots.js b/src/utils/screenshots.js
index 249c57b4..05ba1c2a 100644
--- a/src/utils/screenshots.js
+++ b/src/utils/screenshots.js
@@ -182,7 +182,7 @@ async function imgFileReader(param) {
})
}
-export async function useSetupImageUpload(event, param1, param2, param3) {
+export async function useSetupImageUpload(event, param1, param2, param3, tradeId = null) {
tradeScreenshotChanged.value = true
if (pageId.value == "daily") {
saveButton.value = true
@@ -191,6 +191,7 @@ export async function useSetupImageUpload(event, param1, param2, param3) {
screenshot.dateUnix = param1
screenshot.symbol = param2
screenshot.side = param3
+ screenshot.tradeId = tradeId
}
const file = event.target.files[0];
@@ -429,7 +430,7 @@ export async function useSaveScreenshot() {
//console.log(" -> dateUnix " + screenshot.dateUnix)
- screenshot.side ? screenshot.name = "t" + screenshot.dateUnix + "_" + screenshot.symbol + "_" + screenshot.side : screenshot.name = screenshot.dateUnix + "_" + screenshot.symbol
+ screenshot.tradeId ? screenshot.name = screenshot.tradeId : (screenshot.side ? screenshot.name = "t" + screenshot.dateUnix + "_" + screenshot.symbol + "_" + screenshot.side : screenshot.name = screenshot.dateUnix + "_" + screenshot.symbol)
//console.log("name " + screenshot.name)
diff --git a/src/views/Daily.vue b/src/views/Daily.vue
index 525d3fa2..12bd240e 100644
--- a/src/views/Daily.vue
+++ b/src/views/Daily.vue
@@ -1396,7 +1396,7 @@ function getOHLC(date, symbol, type) {
+ @change="useSetupImageUpload($event, filteredTrades[itemTradeIndex].trades[tradeIndex].entryTime, filteredTrades[itemTradeIndex].trades[tradeIndex].symbol, filteredTrades[itemTradeIndex].trades[tradeIndex].side, filteredTrades[itemTradeIndex].trades[tradeIndex].id)" />
From c881229715e185d554bddd0f6c3fd4844e81dd46 Mon Sep 17 00:00:00 2001
From: mx00 <2784107+mx00@users.noreply.github.com>
Date: Sun, 24 May 2026 22:54:43 -0700
Subject: [PATCH 4/6] Fix screenshot uploads and trade screenshot linking
---
index.mjs | 8 +-
index.mjs.bak | 570 +++++++++++++++++++++++++++++++++++++++
src/utils/screenshots.js | 1 +
3 files changed, 576 insertions(+), 3 deletions(-)
create mode 100644 index.mjs.bak
diff --git a/index.mjs b/index.mjs
index 5c49cfd9..319e5eb3 100644
--- a/index.mjs
+++ b/index.mjs
@@ -46,7 +46,8 @@ console.log(' -> Database URI ' + hiddenDatabaseURI)
let tradenoteDatabase = process.env.TRADENOTE_DATABASE
var app = express();
-app.use(express.json());
+app.use(express.json({ limit: '50mb' }));
+app.use(express.urlencoded({ limit: '50mb', extended: true }));
const port = process.env.TRADENOTE_PORT;
const PROXY_PORT = 39482;
@@ -339,7 +340,8 @@ const setupApiRoutes = (app) => {
- app.use(express.json());
+ app.use(express.json({ limit: '50mb' }));
+app.use(express.urlencoded({ limit: '50mb', extended: true }));
let allUsers
const getAllUsers = async () => {
@@ -498,7 +500,7 @@ const startIndex = async () => {
resolve();
} else {
// In production, handle API routes normally
- app.use('/api/*', express.json(), (req, res, next) => {
+ app.use('/api/*', express.json({ limit: '50mb' }), express.urlencoded({ limit: '50mb', extended: true }), (req, res, next) => {
//console.log(`Received API request: ${req.method} ${req.url}`);
next(); // Pass control to specific API handlers
});
diff --git a/index.mjs.bak b/index.mjs.bak
new file mode 100644
index 00000000..5c49cfd9
--- /dev/null
+++ b/index.mjs.bak
@@ -0,0 +1,570 @@
+import express from 'express';
+import { ParseServer } from 'parse-server'
+//var ParseDashboard = require('parse-dashboard');
+import ParseNode from 'parse/node.js'
+import path from 'path'
+import fs from 'fs'
+import axios from 'axios'
+import * as Vite from 'vite'
+import { MongoClient } from "mongodb"
+import Proxy from 'http-proxy'
+import { useImportTrades, useGetExistingTradesArray, useUploadTrades } from './src/utils/addTrades.js';
+import { currentUser, uploadMfePrices } from './src/stores/globals.js';
+import { useGetTimeZone } from './src/utils/utils.js';
+import Stripe from 'stripe';
+
+
+/* STRIPE VAR */
+let stripeSk //secret key
+let stripePk // public key
+let stripePriceId
+let stripeTrialPeriod
+
+if (process.env.STRIPE_SK) {
+ stripeSk = new Stripe(process.env.STRIPE_SK);
+ stripePk = process.env.STRIPE_PK
+ stripePriceId = process.env.STRIPE_PRICE_ID
+ stripeTrialPeriod = process.env.STRIPE_TRIAL_PERIOD
+}
+
+/* END STRIPE */
+
+let databaseURI
+
+if (process.env.MONGO_URI) {
+ databaseURI = process.env.MONGO_URI
+} else if (process.env.MONGO_ATLAS) {
+ databaseURI = "mongodb+srv://" + process.env.MONGO_USER + ":" + process.env.MONGO_PASSWORD + "@" + process.env.MONGO_URL + "/" + process.env.TRADENOTE_DATABASE + "?authSource=admin"
+} else {
+ databaseURI = "mongodb://" + process.env.MONGO_USER + ":" + process.env.MONGO_PASSWORD + "@" + process.env.MONGO_URL + ":" + process.env.MONGO_PORT + "/" + process.env.TRADENOTE_DATABASE + "?authSource=admin"
+}
+
+console.log("\nCONNECTING TO MONGODB")
+let hiddenDatabaseURI = databaseURI.replace(/:\/\/[^@]*@/, "://***@")
+console.log(' -> Database URI ' + hiddenDatabaseURI)
+
+let tradenoteDatabase = process.env.TRADENOTE_DATABASE
+
+var app = express();
+app.use(express.json());
+
+const port = process.env.TRADENOTE_PORT;
+const PROXY_PORT = 39482;
+
+// SERVER
+
+let server = null
+
+export let allowRegister = false
+
+
+/**************************** APIs ****************************/
+
+const setupApiRoutes = (app) => {
+
+ app.post("/api/parseAppId", (req, res) => {
+ //console.log("\nAPI : post APP ID")
+ //console.log(process.env.APP_ID)
+ res.send(process.env.APP_ID)
+ });
+
+ app.post("/api/registerPage", (req, res) => {
+ //console.log("\nAPI : post APP ID")
+ //console.log(" REGISTER_OFF "+process.env.REGISTER_OFF)
+ res.send(process.env.REGISTER_OFF)
+ });
+
+ app.post("/api/posthog", (req, res) => {
+ //console.log("\nAPI : posthog")
+ if (process.env.ANALYTICS_OFF) {
+ res.send("off")
+ } else {
+ res.send("phc_FxkjH1O898jKu0yiELC3aWKda3vGov7waGN0weU5kw0")
+ }
+ });
+
+
+ /**********************************************
+ * CLOUD / STRIPE
+ **********************************************/
+
+ app.post("/api/checkCloudPayment", async (req, res) => {
+ // Used for checking if can access add*, in case it's a paying user
+ let currentUser = req.body.currentUser
+ //console.log(" currentUser "+JSON.stringify(currentUser))
+ //console.log(" current user " + JSON.stringify(req.body.currentUser))
+ if (process.env.STRIPE_SK) {
+ //console.log("\nAPI : checkCloudPayment")
+ // Check if user is stripe customer
+
+ // Check if user has paying customer
+ if (currentUser.hasOwnProperty("paymentService") && currentUser.paymentService.hasOwnProperty("subscriptionId")) {
+ /// if yes, let inn, status 200
+ const activeSubscription = ['active', 'trialing', 'past_due']
+ const subscription = await stripeSk.subscriptions.retrieve(currentUser.paymentService.subscriptionId)
+ if (activeSubscription.includes(subscription.status)) {
+ console.log(" -> User has valid subscription.");
+ res.status(200).send('OK');
+ } else {
+ console.log(" -> User has invalid subscription.");
+ res.status(403).send('Forbidden');
+ }
+
+ }
+
+ /// If not, check if user is within trial period
+ else {
+
+ // Convert createdAt to a Date object
+ const createdAtDate = new Date(currentUser.createdAt);
+
+ // Get the current time
+ const currentDate = new Date();
+
+ // Calculate the time difference in milliseconds
+ const timeDifference = currentDate - createdAtDate;
+
+ // Convert the time difference to days
+ const differenceInDays = timeDifference / (1000 * 60 * 60 * 24); // Milliseconds to days
+
+ //// if older, redirect to stripe / status 403
+ if (differenceInDays > stripeTrialPeriod) {
+ console.log(" -> User is past trial period.");
+ res.status(403).send('Forbidden');
+ }
+
+ //// else, let inn, status 200
+ else {
+ console.log(" -> User is within trial period.");
+ res.status(200).send('OK');
+ }
+ }
+
+
+
+
+ } else {
+ res.status(200).send('OK');
+ }
+ });
+
+ app.post('/api/create-checkout-session', async (req, res) => {
+
+ let return_url
+ if (process.env.NODE_ENV == 'dev') {
+ return_url = `http://localhost:${port}/checkoutSuccess?session_id={CHECKOUT_SESSION_ID}`
+ }else{
+ return_url = `https://app.tradenote.co/checkoutSuccess?session_id={CHECKOUT_SESSION_ID}`
+ }
+
+ //console.log(" -> return_url : "+return_url)
+ const session = await stripeSk.checkout.sessions.create({
+ ui_mode: 'embedded',
+ line_items: [
+ {
+ // Provide the exact Price ID (for example, pr_1234) of the product you want to sell
+ price: stripePriceId,
+ quantity: 1,
+ },
+ ],
+ mode: 'subscription',
+ return_url: return_url,
+ automatic_tax: { enabled: true },
+ });
+
+ res.send({ clientSecret: session.client_secret });
+ });
+
+ app.get('/api/getStripePk', async (req, res) => {
+ res.status(200).send(stripePk);
+ })
+
+ app.get('/api/session-status', async (req, res) => {
+ try {
+ console.log("Getting session status");
+ const session = await stripeSk.checkout.sessions.retrieve(req.query.session_id);
+
+ //console.log("Session retrieved:", JSON.stringify(session));
+
+ res.send({
+ session: session,
+ status: session.status,
+ customer_email: session.customer_details.email,
+ customer_id: session.customer
+ });
+ } catch (error) {
+ console.error("Error retrieving session:", error.message);
+ res.status(500).send({ error: error.message });
+ }
+ });
+
+ /******************* END CLOUD ****************************/
+
+ app.get('/api/dockerVersion', async (req, res) => {
+ console.log("Getting Docker Version");
+ await axios.get("https://hub.docker.com/v2/namespaces/eleventrading/repositories/tradenote/tags")
+ .then((response) => {
+ //console.log(" -> data " + JSON.stringify(response.data))
+ res.status(200).send(response.data);
+ })
+ .catch((error) => {
+ res.status(500).send({ error: error.message });
+ })
+ .finally(function () {
+ // always executed
+ })
+ });
+
+ app.post("/api/updateSchemas", async (req, res) => {
+
+ if (!process.env.STRIPE_SK || process.env.UPSERT_SCHEMA) {
+ console.log("\nAPI : post update schema")
+
+ let rawdata = fs.readFileSync('requiredClasses.json');
+ let schemasJson = JSON.parse(rawdata);
+ //console.log("schemasJson "+JSON.stringify(schemasJson))
+
+ let existingSchema = []
+ const getExistingSchema = await ParseNode.Schema.all()
+ //console.log(" -> Get existing schema " + JSON.stringify(getExistingSchema))
+
+ /* 1- Rename legacy names in mongoDB */
+ const renameMongoDb = (param1, param2) => {
+ return new Promise(async (resolve, reject) => {
+ console.log(" -> Renaming class " + param1 + " to " + param2)
+ MongoClient.connect(databaseURI).then(async (client) => {
+ console.log(" --> Connected to MongoDB")
+ const connect = client.db(tradenoteDatabase);
+ const allCollections = await connect.listCollections().toArray()
+ //console.log("allCollections "+JSON.stringify(allCollections))
+ let collectionExists = allCollections.filter(obj => obj.name == param1)
+ //console.log(" --> collectionExists "+collectionExists.length)
+ if (collectionExists.length > 0) {
+ const collection = connect.collection(param1);
+ collection.rename(param2).then(() => {
+ console.log(" -> Renamed class successfully");
+ resolve()
+ })
+ } else {
+ console.log(" -> Collection doesn't exist.")
+ resolve()
+ }
+
+ }).catch((err) => {
+ console.log(" -> Error renaming MongoDB class: " + err.Message);
+ reject()
+ })
+ })
+ }
+
+ for (let i = 0; i < getExistingSchema.length; i++) {
+ //console.log("Class name " + getExistingSchema[i].className)
+
+ //we check for classes/collections that need to be renamed
+ if (getExistingSchema[i].className == "setupsEntries" || getExistingSchema[i].className == "journals") {
+ let oldName = getExistingSchema[i].className
+ let newName
+
+ if (getExistingSchema[i].className == "setupsEntries") newName = "screenshots"
+ if (getExistingSchema[i].className == "journals") newName = "diaries"
+ if (getExistingSchema[i].className == "patternsMistakes") newName = "setups"
+
+ await renameMongoDb(oldName, newName)
+ } else {
+ existingSchema.push(getExistingSchema[i].className)
+ }
+ }
+ //console.log(" -> Existing Schema " + existingSchema)
+
+ /* 2- Update or save new schemas in mongoDB */
+ const updateSaveSchema = (param1, param2, param3) => {
+ return new Promise((resolve, reject) => {
+ const mySchema = new ParseNode.Schema(param1);
+ if (param2[param3].type === "String") mySchema.addString(param3)
+ if (param2[param3].type === "Number") mySchema.addNumber(param3)
+ if (param2[param3].type === "Boolean") mySchema.addBoolean(param3)
+ if (param2[param3].type === "Date") mySchema.addDate(param3)
+ if (param2[param3].type === "File") mySchema.addFile(param3)
+ if (param2[param3].type === "GeoPoint") mySchema.addGeoPoint(param3)
+ if (param2[param3].type === "Polygon") mySchema.addPolygon(param3)
+ if (param2[param3].type === "Array") mySchema.addArray(param3)
+ if (param2[param3].type === "Object") mySchema.addObject(param3)
+ if (param2[param3].type === "Pointer") mySchema.addPointer(param3, param2[param3].targetClass)
+ if (param2[param3].type === "Relation") mySchema.addRelation(param3, param2[param3].targetClass)
+
+ //console.log("existing schema "+existingSchema)
+ //console.log("includes ? "+existingSchema.includes(className))
+
+ //If ParseNode (existing) schema includes the class name from required classes then update (just in case). Else add, and then add that class to existing schema array
+ if (existingSchema.includes(param1)) {
+ mySchema.update().then((result) => {
+ console.log(" --> Updating field " + param3)
+ //console.log(" -> Updated schema " + JSON.stringify(result))
+ resolve()
+ })
+ } else {
+ mySchema.save().then((result) => {
+ //console.log(" -> Save new schema " + JSON.stringify(result))
+ console.log(" --> Saving field " + param3)
+ existingSchema.push(param1) // Once saved, we update for the rest of the fields, so we need to push to existingSchema
+ //console.log(" -> Existing Schema " + existingSchema)
+ resolve()
+ })
+ }
+ })
+ }
+
+ for (let i = 0; i < schemasJson.length; i++) {
+ //console.log("el " + schemasJson[i].className)
+ let className = schemasJson[i].className
+ console.log(" -> Upsert class/collection " + className + " in ParseNode Schema")
+ let obj = schemasJson[i].fields
+ for (const key of Object.keys(obj)) {
+ //console.log(key, obj[key]);
+ if (key != "objectId" && key != "updatedAt" && key != "createdAt" && key != "ACL") {
+ //console.log(" -> Key " + key)
+ await updateSaveSchema(className, obj, key)
+ }
+
+ }
+ }
+
+ res.send({ "existingSchema": existingSchema })
+ } else {
+ res.status(200).send('OK');
+ }
+
+ })
+
+
+
+
+ app.use(express.json());
+
+ let allUsers
+ const getAllUsers = async () => {
+ console.log(" -> Getting all users")
+ return new Promise(async (resolve, reject) => {
+ const parseObject = ParseNode.Object.extend("_User");
+ const query = new ParseNode.Query(parseObject);
+ const results = await query.find({ useMasterKey: true });
+ allUsers = JSON.parse(JSON.stringify(results))
+ resolve()
+ })
+ }
+
+ const validateApiKey = async (req, res, next) => {
+ await getAllUsers()
+ const targetKey = req.headers['api-key'] || req.query['api-key'];
+ //console.log(" -> target Key " + targetKey)
+
+ const checkIPKey = (allUsers, targetKey) => {
+ for (const user of Object.values(allUsers)) {
+ if (user.hasOwnProperty("apis")) {
+ const index = user.apis.findIndex(obj => obj.key === targetKey);
+ if (index !== -1) {
+ currentUser.value = user
+ return true;
+ }
+ }
+
+ }
+ return -1; // Return -1 if not found
+ }
+
+ // Usage example
+ const hasIPKey = checkIPKey(allUsers, targetKey);
+
+ if (hasIPKey) {
+ console.log(" -> Valid api key found :)")
+ next();
+ } else {
+ console.log(" -> Invalid api key")
+ return res.status(401).send({ error: 'Invalid API key' });
+ }
+ }
+
+ app.post('/api/trades', validateApiKey, async (req, res) => {
+ const data = req.body;
+ try {
+ if (data && !data.data.length > 0) {
+ res.status(200).send(" -> No trades to import");
+ }
+ else {
+
+ uploadMfePrices.value = data.uploadMfePrices
+
+ //console.log(" uploadMfePrices "+uploadMfePrices.value)
+ // Call the function from addTrades.js
+
+ await useGetTimeZone()
+ await useGetExistingTradesArray("api", ParseNode)
+ await useImportTrades(data.data, "api", data.selectedBroker, ParseNode)
+ await useUploadTrades("api", ParseNode)
+
+ res.status(200).send(" -> Saved Trades to ParseNode DB");
+ }
+ } catch (error) {
+ console.error(error);
+ res.status(500).send({ error: 'Error creating executions' });
+ }
+ });
+
+ app.post('/api/databento', async (req, res) => {
+ //console.log(" calling databento")
+ const data = req.body;
+ //console.log(" data "+JSON.stringify(data))
+ try {
+ const username = data.username;
+ const password = '';
+
+
+ let config = {
+ method: 'post',
+ maxBodyLength: Infinity,
+ url: "https://hist.databento.com/v0/timeseries.get_range",
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'Authorization': 'Basic ' + Buffer.from(username + ':' + password).toString('base64')
+ },
+ data: data
+ };
+
+ let responseBack
+ axios.request(config)
+ .then((response) => {
+ //console.log("\n -> Resp " + response.data)
+ responseBack = response.data
+ res.status(200).send(responseBack);
+ })
+ .catch((error) => {
+ console.log(error);
+ res.status(500).send({ error: error });
+ });
+
+ } catch (error) {
+ console.error(error);
+ res.status(500).send({ error: error });
+ }
+ })
+};
+
+/**************************** END APIs ****************************/
+
+const startIndex = async () => {
+
+ const startServer = async () => {
+ console.log("\nSTARTING NODEJS SERVER")
+ return new Promise(async (resolve, reject) => {
+ server = app.listen(port, function () {
+ console.log(' -> TradeNote server started on http://localhost:' + port)
+ });
+ resolve(server)
+ })
+ }
+
+ const runServer = async () => {
+ console.log("\nRUNNING SERVER");
+
+ return new Promise(async (resolve, reject) => {
+ if (process.env.NODE_ENV == 'dev') {
+ // Set up proxy for development environment
+ const proxy = new Proxy.createProxyServer({
+ target: { host: 'localhost', port: PROXY_PORT },
+ });
+
+ // Middleware to handle API routes first (do not pass to Vite)
+ app.use('/api/*', (req, res, next) => {
+ // Handle API routes here
+ //console.log("Handling API route:", req.url);
+ next(); // Continue processing the request for /api/* routes
+ });
+
+ // Set up API routes for dev mode as well
+ setupApiRoutes(app);
+
+ // Proxy all other routes (non-API) to Vite
+ app.use((req, res, next) => {
+ if (req.url.startsWith('/api/')) {
+ return next(); // Let the /api/* routes be handled by the previous middleware
+ }
+ proxy.web(req, res); // Proxy all other routes to Vite
+ });
+
+ // Start Vite dev server
+ const vite = await Vite.createServer({ server: { port: PROXY_PORT } });
+ vite.listen();
+ console.log(" -> Running vite dev server");
+ resolve();
+ } else {
+ // In production, handle API routes normally
+ app.use('/api/*', express.json(), (req, res, next) => {
+ //console.log(`Received API request: ${req.method} ${req.url}`);
+ next(); // Pass control to specific API handlers
+ });
+
+ // Set up API routes for production
+ setupApiRoutes(app);
+
+ // Serve static files from 'dist' folder
+ app.use(express.static('dist'));
+
+ // Fallback for SPA
+ app.get('*', (req, res) => {
+ res.sendFile(path.resolve('dist', 'index.html'));
+ });
+ console.log(" -> Running prod server");
+ resolve();
+ }
+ });
+ };
+
+
+ const setupParseServer = async () => {
+ console.log("\nSTARTING PARSE SERVER")
+ return new Promise(async (resolve, reject) => {
+ const serv = new ParseServer({
+ databaseURI: databaseURI,
+ appId: process.env.APP_ID,
+ masterKey: process.env.MASTER_KEY,
+ port: port,
+ masterKeyIps: ['0.0.0.0/0', '::/0'],
+ allowClientClassCreation: false,
+ allowExpiredAuthDataToken: false
+ });
+
+ // EXPRESS USE
+ await serv.start().then(() => {
+ app.use('/parse', serv.app);
+ console.log(" -> ParseNode server started")
+ resolve()
+ })
+ })
+ }
+
+ await startServer()
+ await setupParseServer()
+ await runServer()
+
+ /*var parseDashboard = new ParseDashboard({
+ "apps": [{
+ "serverURL": "/parse",
+ "appId": process.env.APP_ID,
+ "masterKey": process.env.MASTER_KEY,
+ "appName": "TradeNote"
+ }],
+ "trustProxy": true
+ });*/
+
+
+
+ if (process.env.PARSE_DASHBOARD) app.use('/parseDashboard', parseDashboard)
+
+ //INIT
+ //console.log("\nInitializing ParseNode")
+ ParseNode.initialize(process.env.APP_ID)
+ ParseNode.serverURL = "http://localhost:" + port + "/parse"
+ ParseNode.masterKey = process.env.MASTER_KEY
+
+}
+startIndex()
diff --git a/src/utils/screenshots.js b/src/utils/screenshots.js
index 05ba1c2a..30a88346 100644
--- a/src/utils/screenshots.js
+++ b/src/utils/screenshots.js
@@ -192,6 +192,7 @@ export async function useSetupImageUpload(event, param1, param2, param3, tradeId
screenshot.symbol = param2
screenshot.side = param3
screenshot.tradeId = tradeId
+ screenshot.objectId = undefined
}
const file = event.target.files[0];
From 022c01e704a5a260bce6395d4bd3e2a8ea813c9a Mon Sep 17 00:00:00 2001
From: mx00 <2784107+mx00@users.noreply.github.com>
Date: Sun, 24 May 2026 23:54:52 -0700
Subject: [PATCH 5/6] Support multiple screenshots per trade modal
---
index.mjs.bak | 570 --------------------------------------------
src/views/Daily.vue | 16 +-
2 files changed, 10 insertions(+), 576 deletions(-)
delete mode 100644 index.mjs.bak
diff --git a/index.mjs.bak b/index.mjs.bak
deleted file mode 100644
index 5c49cfd9..00000000
--- a/index.mjs.bak
+++ /dev/null
@@ -1,570 +0,0 @@
-import express from 'express';
-import { ParseServer } from 'parse-server'
-//var ParseDashboard = require('parse-dashboard');
-import ParseNode from 'parse/node.js'
-import path from 'path'
-import fs from 'fs'
-import axios from 'axios'
-import * as Vite from 'vite'
-import { MongoClient } from "mongodb"
-import Proxy from 'http-proxy'
-import { useImportTrades, useGetExistingTradesArray, useUploadTrades } from './src/utils/addTrades.js';
-import { currentUser, uploadMfePrices } from './src/stores/globals.js';
-import { useGetTimeZone } from './src/utils/utils.js';
-import Stripe from 'stripe';
-
-
-/* STRIPE VAR */
-let stripeSk //secret key
-let stripePk // public key
-let stripePriceId
-let stripeTrialPeriod
-
-if (process.env.STRIPE_SK) {
- stripeSk = new Stripe(process.env.STRIPE_SK);
- stripePk = process.env.STRIPE_PK
- stripePriceId = process.env.STRIPE_PRICE_ID
- stripeTrialPeriod = process.env.STRIPE_TRIAL_PERIOD
-}
-
-/* END STRIPE */
-
-let databaseURI
-
-if (process.env.MONGO_URI) {
- databaseURI = process.env.MONGO_URI
-} else if (process.env.MONGO_ATLAS) {
- databaseURI = "mongodb+srv://" + process.env.MONGO_USER + ":" + process.env.MONGO_PASSWORD + "@" + process.env.MONGO_URL + "/" + process.env.TRADENOTE_DATABASE + "?authSource=admin"
-} else {
- databaseURI = "mongodb://" + process.env.MONGO_USER + ":" + process.env.MONGO_PASSWORD + "@" + process.env.MONGO_URL + ":" + process.env.MONGO_PORT + "/" + process.env.TRADENOTE_DATABASE + "?authSource=admin"
-}
-
-console.log("\nCONNECTING TO MONGODB")
-let hiddenDatabaseURI = databaseURI.replace(/:\/\/[^@]*@/, "://***@")
-console.log(' -> Database URI ' + hiddenDatabaseURI)
-
-let tradenoteDatabase = process.env.TRADENOTE_DATABASE
-
-var app = express();
-app.use(express.json());
-
-const port = process.env.TRADENOTE_PORT;
-const PROXY_PORT = 39482;
-
-// SERVER
-
-let server = null
-
-export let allowRegister = false
-
-
-/**************************** APIs ****************************/
-
-const setupApiRoutes = (app) => {
-
- app.post("/api/parseAppId", (req, res) => {
- //console.log("\nAPI : post APP ID")
- //console.log(process.env.APP_ID)
- res.send(process.env.APP_ID)
- });
-
- app.post("/api/registerPage", (req, res) => {
- //console.log("\nAPI : post APP ID")
- //console.log(" REGISTER_OFF "+process.env.REGISTER_OFF)
- res.send(process.env.REGISTER_OFF)
- });
-
- app.post("/api/posthog", (req, res) => {
- //console.log("\nAPI : posthog")
- if (process.env.ANALYTICS_OFF) {
- res.send("off")
- } else {
- res.send("phc_FxkjH1O898jKu0yiELC3aWKda3vGov7waGN0weU5kw0")
- }
- });
-
-
- /**********************************************
- * CLOUD / STRIPE
- **********************************************/
-
- app.post("/api/checkCloudPayment", async (req, res) => {
- // Used for checking if can access add*, in case it's a paying user
- let currentUser = req.body.currentUser
- //console.log(" currentUser "+JSON.stringify(currentUser))
- //console.log(" current user " + JSON.stringify(req.body.currentUser))
- if (process.env.STRIPE_SK) {
- //console.log("\nAPI : checkCloudPayment")
- // Check if user is stripe customer
-
- // Check if user has paying customer
- if (currentUser.hasOwnProperty("paymentService") && currentUser.paymentService.hasOwnProperty("subscriptionId")) {
- /// if yes, let inn, status 200
- const activeSubscription = ['active', 'trialing', 'past_due']
- const subscription = await stripeSk.subscriptions.retrieve(currentUser.paymentService.subscriptionId)
- if (activeSubscription.includes(subscription.status)) {
- console.log(" -> User has valid subscription.");
- res.status(200).send('OK');
- } else {
- console.log(" -> User has invalid subscription.");
- res.status(403).send('Forbidden');
- }
-
- }
-
- /// If not, check if user is within trial period
- else {
-
- // Convert createdAt to a Date object
- const createdAtDate = new Date(currentUser.createdAt);
-
- // Get the current time
- const currentDate = new Date();
-
- // Calculate the time difference in milliseconds
- const timeDifference = currentDate - createdAtDate;
-
- // Convert the time difference to days
- const differenceInDays = timeDifference / (1000 * 60 * 60 * 24); // Milliseconds to days
-
- //// if older, redirect to stripe / status 403
- if (differenceInDays > stripeTrialPeriod) {
- console.log(" -> User is past trial period.");
- res.status(403).send('Forbidden');
- }
-
- //// else, let inn, status 200
- else {
- console.log(" -> User is within trial period.");
- res.status(200).send('OK');
- }
- }
-
-
-
-
- } else {
- res.status(200).send('OK');
- }
- });
-
- app.post('/api/create-checkout-session', async (req, res) => {
-
- let return_url
- if (process.env.NODE_ENV == 'dev') {
- return_url = `http://localhost:${port}/checkoutSuccess?session_id={CHECKOUT_SESSION_ID}`
- }else{
- return_url = `https://app.tradenote.co/checkoutSuccess?session_id={CHECKOUT_SESSION_ID}`
- }
-
- //console.log(" -> return_url : "+return_url)
- const session = await stripeSk.checkout.sessions.create({
- ui_mode: 'embedded',
- line_items: [
- {
- // Provide the exact Price ID (for example, pr_1234) of the product you want to sell
- price: stripePriceId,
- quantity: 1,
- },
- ],
- mode: 'subscription',
- return_url: return_url,
- automatic_tax: { enabled: true },
- });
-
- res.send({ clientSecret: session.client_secret });
- });
-
- app.get('/api/getStripePk', async (req, res) => {
- res.status(200).send(stripePk);
- })
-
- app.get('/api/session-status', async (req, res) => {
- try {
- console.log("Getting session status");
- const session = await stripeSk.checkout.sessions.retrieve(req.query.session_id);
-
- //console.log("Session retrieved:", JSON.stringify(session));
-
- res.send({
- session: session,
- status: session.status,
- customer_email: session.customer_details.email,
- customer_id: session.customer
- });
- } catch (error) {
- console.error("Error retrieving session:", error.message);
- res.status(500).send({ error: error.message });
- }
- });
-
- /******************* END CLOUD ****************************/
-
- app.get('/api/dockerVersion', async (req, res) => {
- console.log("Getting Docker Version");
- await axios.get("https://hub.docker.com/v2/namespaces/eleventrading/repositories/tradenote/tags")
- .then((response) => {
- //console.log(" -> data " + JSON.stringify(response.data))
- res.status(200).send(response.data);
- })
- .catch((error) => {
- res.status(500).send({ error: error.message });
- })
- .finally(function () {
- // always executed
- })
- });
-
- app.post("/api/updateSchemas", async (req, res) => {
-
- if (!process.env.STRIPE_SK || process.env.UPSERT_SCHEMA) {
- console.log("\nAPI : post update schema")
-
- let rawdata = fs.readFileSync('requiredClasses.json');
- let schemasJson = JSON.parse(rawdata);
- //console.log("schemasJson "+JSON.stringify(schemasJson))
-
- let existingSchema = []
- const getExistingSchema = await ParseNode.Schema.all()
- //console.log(" -> Get existing schema " + JSON.stringify(getExistingSchema))
-
- /* 1- Rename legacy names in mongoDB */
- const renameMongoDb = (param1, param2) => {
- return new Promise(async (resolve, reject) => {
- console.log(" -> Renaming class " + param1 + " to " + param2)
- MongoClient.connect(databaseURI).then(async (client) => {
- console.log(" --> Connected to MongoDB")
- const connect = client.db(tradenoteDatabase);
- const allCollections = await connect.listCollections().toArray()
- //console.log("allCollections "+JSON.stringify(allCollections))
- let collectionExists = allCollections.filter(obj => obj.name == param1)
- //console.log(" --> collectionExists "+collectionExists.length)
- if (collectionExists.length > 0) {
- const collection = connect.collection(param1);
- collection.rename(param2).then(() => {
- console.log(" -> Renamed class successfully");
- resolve()
- })
- } else {
- console.log(" -> Collection doesn't exist.")
- resolve()
- }
-
- }).catch((err) => {
- console.log(" -> Error renaming MongoDB class: " + err.Message);
- reject()
- })
- })
- }
-
- for (let i = 0; i < getExistingSchema.length; i++) {
- //console.log("Class name " + getExistingSchema[i].className)
-
- //we check for classes/collections that need to be renamed
- if (getExistingSchema[i].className == "setupsEntries" || getExistingSchema[i].className == "journals") {
- let oldName = getExistingSchema[i].className
- let newName
-
- if (getExistingSchema[i].className == "setupsEntries") newName = "screenshots"
- if (getExistingSchema[i].className == "journals") newName = "diaries"
- if (getExistingSchema[i].className == "patternsMistakes") newName = "setups"
-
- await renameMongoDb(oldName, newName)
- } else {
- existingSchema.push(getExistingSchema[i].className)
- }
- }
- //console.log(" -> Existing Schema " + existingSchema)
-
- /* 2- Update or save new schemas in mongoDB */
- const updateSaveSchema = (param1, param2, param3) => {
- return new Promise((resolve, reject) => {
- const mySchema = new ParseNode.Schema(param1);
- if (param2[param3].type === "String") mySchema.addString(param3)
- if (param2[param3].type === "Number") mySchema.addNumber(param3)
- if (param2[param3].type === "Boolean") mySchema.addBoolean(param3)
- if (param2[param3].type === "Date") mySchema.addDate(param3)
- if (param2[param3].type === "File") mySchema.addFile(param3)
- if (param2[param3].type === "GeoPoint") mySchema.addGeoPoint(param3)
- if (param2[param3].type === "Polygon") mySchema.addPolygon(param3)
- if (param2[param3].type === "Array") mySchema.addArray(param3)
- if (param2[param3].type === "Object") mySchema.addObject(param3)
- if (param2[param3].type === "Pointer") mySchema.addPointer(param3, param2[param3].targetClass)
- if (param2[param3].type === "Relation") mySchema.addRelation(param3, param2[param3].targetClass)
-
- //console.log("existing schema "+existingSchema)
- //console.log("includes ? "+existingSchema.includes(className))
-
- //If ParseNode (existing) schema includes the class name from required classes then update (just in case). Else add, and then add that class to existing schema array
- if (existingSchema.includes(param1)) {
- mySchema.update().then((result) => {
- console.log(" --> Updating field " + param3)
- //console.log(" -> Updated schema " + JSON.stringify(result))
- resolve()
- })
- } else {
- mySchema.save().then((result) => {
- //console.log(" -> Save new schema " + JSON.stringify(result))
- console.log(" --> Saving field " + param3)
- existingSchema.push(param1) // Once saved, we update for the rest of the fields, so we need to push to existingSchema
- //console.log(" -> Existing Schema " + existingSchema)
- resolve()
- })
- }
- })
- }
-
- for (let i = 0; i < schemasJson.length; i++) {
- //console.log("el " + schemasJson[i].className)
- let className = schemasJson[i].className
- console.log(" -> Upsert class/collection " + className + " in ParseNode Schema")
- let obj = schemasJson[i].fields
- for (const key of Object.keys(obj)) {
- //console.log(key, obj[key]);
- if (key != "objectId" && key != "updatedAt" && key != "createdAt" && key != "ACL") {
- //console.log(" -> Key " + key)
- await updateSaveSchema(className, obj, key)
- }
-
- }
- }
-
- res.send({ "existingSchema": existingSchema })
- } else {
- res.status(200).send('OK');
- }
-
- })
-
-
-
-
- app.use(express.json());
-
- let allUsers
- const getAllUsers = async () => {
- console.log(" -> Getting all users")
- return new Promise(async (resolve, reject) => {
- const parseObject = ParseNode.Object.extend("_User");
- const query = new ParseNode.Query(parseObject);
- const results = await query.find({ useMasterKey: true });
- allUsers = JSON.parse(JSON.stringify(results))
- resolve()
- })
- }
-
- const validateApiKey = async (req, res, next) => {
- await getAllUsers()
- const targetKey = req.headers['api-key'] || req.query['api-key'];
- //console.log(" -> target Key " + targetKey)
-
- const checkIPKey = (allUsers, targetKey) => {
- for (const user of Object.values(allUsers)) {
- if (user.hasOwnProperty("apis")) {
- const index = user.apis.findIndex(obj => obj.key === targetKey);
- if (index !== -1) {
- currentUser.value = user
- return true;
- }
- }
-
- }
- return -1; // Return -1 if not found
- }
-
- // Usage example
- const hasIPKey = checkIPKey(allUsers, targetKey);
-
- if (hasIPKey) {
- console.log(" -> Valid api key found :)")
- next();
- } else {
- console.log(" -> Invalid api key")
- return res.status(401).send({ error: 'Invalid API key' });
- }
- }
-
- app.post('/api/trades', validateApiKey, async (req, res) => {
- const data = req.body;
- try {
- if (data && !data.data.length > 0) {
- res.status(200).send(" -> No trades to import");
- }
- else {
-
- uploadMfePrices.value = data.uploadMfePrices
-
- //console.log(" uploadMfePrices "+uploadMfePrices.value)
- // Call the function from addTrades.js
-
- await useGetTimeZone()
- await useGetExistingTradesArray("api", ParseNode)
- await useImportTrades(data.data, "api", data.selectedBroker, ParseNode)
- await useUploadTrades("api", ParseNode)
-
- res.status(200).send(" -> Saved Trades to ParseNode DB");
- }
- } catch (error) {
- console.error(error);
- res.status(500).send({ error: 'Error creating executions' });
- }
- });
-
- app.post('/api/databento', async (req, res) => {
- //console.log(" calling databento")
- const data = req.body;
- //console.log(" data "+JSON.stringify(data))
- try {
- const username = data.username;
- const password = '';
-
-
- let config = {
- method: 'post',
- maxBodyLength: Infinity,
- url: "https://hist.databento.com/v0/timeseries.get_range",
- headers: {
- 'Content-Type': 'application/x-www-form-urlencoded',
- 'Authorization': 'Basic ' + Buffer.from(username + ':' + password).toString('base64')
- },
- data: data
- };
-
- let responseBack
- axios.request(config)
- .then((response) => {
- //console.log("\n -> Resp " + response.data)
- responseBack = response.data
- res.status(200).send(responseBack);
- })
- .catch((error) => {
- console.log(error);
- res.status(500).send({ error: error });
- });
-
- } catch (error) {
- console.error(error);
- res.status(500).send({ error: error });
- }
- })
-};
-
-/**************************** END APIs ****************************/
-
-const startIndex = async () => {
-
- const startServer = async () => {
- console.log("\nSTARTING NODEJS SERVER")
- return new Promise(async (resolve, reject) => {
- server = app.listen(port, function () {
- console.log(' -> TradeNote server started on http://localhost:' + port)
- });
- resolve(server)
- })
- }
-
- const runServer = async () => {
- console.log("\nRUNNING SERVER");
-
- return new Promise(async (resolve, reject) => {
- if (process.env.NODE_ENV == 'dev') {
- // Set up proxy for development environment
- const proxy = new Proxy.createProxyServer({
- target: { host: 'localhost', port: PROXY_PORT },
- });
-
- // Middleware to handle API routes first (do not pass to Vite)
- app.use('/api/*', (req, res, next) => {
- // Handle API routes here
- //console.log("Handling API route:", req.url);
- next(); // Continue processing the request for /api/* routes
- });
-
- // Set up API routes for dev mode as well
- setupApiRoutes(app);
-
- // Proxy all other routes (non-API) to Vite
- app.use((req, res, next) => {
- if (req.url.startsWith('/api/')) {
- return next(); // Let the /api/* routes be handled by the previous middleware
- }
- proxy.web(req, res); // Proxy all other routes to Vite
- });
-
- // Start Vite dev server
- const vite = await Vite.createServer({ server: { port: PROXY_PORT } });
- vite.listen();
- console.log(" -> Running vite dev server");
- resolve();
- } else {
- // In production, handle API routes normally
- app.use('/api/*', express.json(), (req, res, next) => {
- //console.log(`Received API request: ${req.method} ${req.url}`);
- next(); // Pass control to specific API handlers
- });
-
- // Set up API routes for production
- setupApiRoutes(app);
-
- // Serve static files from 'dist' folder
- app.use(express.static('dist'));
-
- // Fallback for SPA
- app.get('*', (req, res) => {
- res.sendFile(path.resolve('dist', 'index.html'));
- });
- console.log(" -> Running prod server");
- resolve();
- }
- });
- };
-
-
- const setupParseServer = async () => {
- console.log("\nSTARTING PARSE SERVER")
- return new Promise(async (resolve, reject) => {
- const serv = new ParseServer({
- databaseURI: databaseURI,
- appId: process.env.APP_ID,
- masterKey: process.env.MASTER_KEY,
- port: port,
- masterKeyIps: ['0.0.0.0/0', '::/0'],
- allowClientClassCreation: false,
- allowExpiredAuthDataToken: false
- });
-
- // EXPRESS USE
- await serv.start().then(() => {
- app.use('/parse', serv.app);
- console.log(" -> ParseNode server started")
- resolve()
- })
- })
- }
-
- await startServer()
- await setupParseServer()
- await runServer()
-
- /*var parseDashboard = new ParseDashboard({
- "apps": [{
- "serverURL": "/parse",
- "appId": process.env.APP_ID,
- "masterKey": process.env.MASTER_KEY,
- "appName": "TradeNote"
- }],
- "trustProxy": true
- });*/
-
-
-
- if (process.env.PARSE_DASHBOARD) app.use('/parseDashboard', parseDashboard)
-
- //INIT
- //console.log("\nInitializing ParseNode")
- ParseNode.initialize(process.env.APP_ID)
- ParseNode.serverURL = "http://localhost:" + port + "/parse"
- ParseNode.masterKey = process.env.MASTER_KEY
-
-}
-startIndex()
diff --git a/src/views/Daily.vue b/src/views/Daily.vue
index 12bd240e..3b1195ea 100644
--- a/src/views/Daily.vue
+++ b/src/views/Daily.vue
@@ -212,19 +212,21 @@ async function clickTradesModal(param1, param2, param3) {
await Promise.all([resetExcursion(), useResetTags()])
//For setups I have added setups into filteredTrades. For screenshots and excursions I need to find so I create on each modal page a screenshot and excursion object
- let findScreenshot = screenshots.find(obj => obj.name == filteredTradeId)
+ let findScreenshots = screenshots.filter(obj => obj.name == filteredTradeId)
for (let key in screenshot) delete screenshot[key]
candlestickChartFailureMessage.value = null // to avoid message when screenshot is present
- if (findScreenshot) {
+ if (findScreenshots.length > 0) {
//console.log(" found screenshot")
- for (let key in findScreenshot) {
- screenshot[key] = findScreenshot[key]
+ screenshot.multiple = findScreenshots
+ for (let key in findScreenshots[0]) {
+ screenshot[key] = findScreenshots[0][key]
}
} else {
//console.log(" did not find any screenshot")
screenshot.side = null
screenshot.type = null
+ screenshot.multiple = []
/* GET OHLC / CANDLESTICK CHARTS */
@@ -1190,8 +1192,10 @@ function getOHLC(date, symbol, type) {
-
-
+
From db89b49281ee893ef945b8152689e726c86d3090 Mon Sep 17 00:00:00 2001
From: mx00 <2784107+mx00@users.noreply.github.com>
Date: Mon, 25 May 2026 00:43:23 -0700
Subject: [PATCH 6/6] Add timezone selector and expanded timezone list
---
patch-timezones.py | 60 ++++++++++++++++++++++++++++++++++++++++++
src/stores/globals.js | 2 +-
src/views/Settings.vue | 22 +++++++++++++---
3 files changed, 80 insertions(+), 4 deletions(-)
create mode 100644 patch-timezones.py
diff --git a/patch-timezones.py b/patch-timezones.py
new file mode 100644
index 00000000..6f409104
--- /dev/null
+++ b/patch-timezones.py
@@ -0,0 +1,60 @@
+from pathlib import Path
+import re
+
+file_path = Path("src/stores/globals.js")
+
+main_timezones = [
+ "America/New_York",
+ "America/Los_Angeles",
+ "America/Chicago",
+ "America/Denver",
+ "America/Phoenix",
+ "America/Anchorage",
+ "Pacific/Honolulu",
+ "America/Toronto",
+ "America/Vancouver",
+ "America/Mexico_City",
+ "America/Sao_Paulo",
+ "UTC",
+ "Europe/London",
+ "Europe/Dublin",
+ "Europe/Brussels",
+ "Europe/Paris",
+ "Europe/Berlin",
+ "Europe/Madrid",
+ "Europe/Rome",
+ "Europe/Amsterdam",
+ "Europe/Zurich",
+ "Europe/Stockholm",
+ "Europe/Warsaw",
+ "Europe/Moscow",
+ "Asia/Riyadh",
+ "Asia/Dubai",
+ "Asia/Kolkata",
+ "Asia/Bangkok",
+ "Asia/Singapore",
+ "Asia/Shanghai",
+ "Asia/Hong_Kong",
+ "Asia/Tokyo",
+ "Asia/Seoul",
+ "Australia/Sydney",
+ "Australia/Melbourne",
+ "Pacific/Auckland",
+]
+
+s = file_path.read_text()
+
+new_line = 'export const timeZones = ref(' + repr(main_timezones).replace("'", '"') + ')'
+
+s2, n = re.subn(
+ r'export const timeZones = ref\(\[[^\]]*\]\)',
+ new_line,
+ s,
+ count=1,
+)
+
+if n != 1:
+ raise SystemExit("Could not find timeZones array in src/stores/globals.js")
+
+file_path.write_text(s2)
+print("Updated timeZones in", file_path)
diff --git a/src/stores/globals.js b/src/stores/globals.js
index b2c3f3f8..438dac5d 100644
--- a/src/stores/globals.js
+++ b/src/stores/globals.js
@@ -7,7 +7,7 @@ export const registerOff = ref(false)
export const parseId = ref()
export const pageId = ref()
export const currentUser = ref()
-export const timeZones = ref(["America/New_York", "Asia/Shanghai", "Europe/Brussels", "Asia/Tokyo", "Asia/Hong_Kong", "Asia/Kolkata", "Europe/London", "Asia/Riyadh"])
+export const timeZones = ref(["America/New_York", "America/Los_Angeles", "America/Chicago", "America/Denver", "America/Phoenix", "America/Anchorage", "Pacific/Honolulu", "America/Toronto", "America/Vancouver", "America/Mexico_City", "America/Sao_Paulo", "UTC", "Europe/London", "Europe/Dublin", "Europe/Brussels", "Europe/Paris", "Europe/Berlin", "Europe/Madrid", "Europe/Rome", "Europe/Amsterdam", "Europe/Zurich", "Europe/Stockholm", "Europe/Warsaw", "Europe/Moscow", "Asia/Riyadh", "Asia/Dubai", "Asia/Kolkata", "Asia/Bangkok", "Asia/Singapore", "Asia/Shanghai", "Asia/Hong_Kong", "Asia/Tokyo", "Asia/Seoul", "Australia/Sydney", "Australia/Melbourne", "Pacific/Auckland"])
export const timeZoneTrade = ref()
export const queryLimit = ref(10000000)
export const queryLimitExistingTrades = ref(50)
diff --git a/src/views/Settings.vue b/src/views/Settings.vue
index 1673a9bb..f132b13c 100644
--- a/src/views/Settings.vue
+++ b/src/views/Settings.vue
@@ -1,7 +1,7 @@
|