diff --git a/.gitignore b/.gitignore
index 724549c..68a9599 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,7 @@
*.key
*.env
*secrets.js
+secrets.js
trahn-trade-frontend/src/auth/secrets.js*
# ============================================
diff --git a/trahn-trade-backend/internal/repository/price.go b/trahn-trade-backend/internal/repository/price.go
index 021a2f3..77f1dca 100644
--- a/trahn-trade-backend/internal/repository/price.go
+++ b/trahn-trade-backend/internal/repository/price.go
@@ -40,7 +40,7 @@ func (r *PriceRepo) GetByDay(ctx context.Context, tradingDay string) ([]models.P
func (r *PriceRepo) GetAvailableDays(ctx context.Context) ([]string, error) {
rows, err := r.pool.Query(ctx,
- `SELECT DISTINCT trading_day FROM price_history ORDER BY trading_day DESC LIMIT 30`,
+ `SELECT DISTINCT trading_day FROM price_history ORDER BY trading_day ASC LIMIT 30`,
)
if err != nil {
return nil, err
diff --git a/trahn-trade-frontend/index.html b/trahn-trade-frontend/index.html
index 8bf5084..79b13ce 100644
--- a/trahn-trade-frontend/index.html
+++ b/trahn-trade-frontend/index.html
@@ -5,8 +5,6 @@
TRAHN Grid Trader
-
-
diff --git a/trahn-trade-frontend/src/App.css b/trahn-trade-frontend/src/App.css
index 3fff0d5..37e9cd5 100644
--- a/trahn-trade-frontend/src/App.css
+++ b/trahn-trade-frontend/src/App.css
@@ -1,612 +1,650 @@
:root {
- --bg-primary: #0d1117;
- --bg-secondary: #161b22;
- --bg-tertiary: #21262d;
- --text-primary: #e6edf3;
- --text-secondary: #8b949e;
- --text-muted: #484f58;
- --accent: #58a6ff;
- --green: #3fb950;
- --green-fill: rgba(63, 185, 80, 0.15);
- --red: #f85149;
- --red-fill: rgba(248, 81, 73, 0.15);
- --yellow: #f0c000;
- --orange: #db6d28;
- --border: #30363d;
+ --bg-primary: #000000;
+ --bg-secondary: #070808;
+ --bg-tertiary: #363737;
+ --text-primary: #01ff3c;
+ --text-secondary: #01ff3c;
+ --text-muted: #484f58;
+ --accent: #58a6ff;
+ --green: #3fb950;
+ --green-fill: rgba(63, 185, 80, 0.15);
+ --red: #f85149;
+ --red-fill: rgba(248, 81, 73, 0.15);
+ --yellow: #f0c000;
+ --orange: #db6d28;
+ --border: #30363d;
}
* {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
}
body {
- font-family: 'Space Grotesk', -apple-system, BlinkMacSystemFont, sans-serif;
- background: var(--bg-primary);
- color: var(--text-primary);
- min-height: 100vh;
- padding: 2rem;
+ font-family: 'Ac437_Rainbow100_re_132', monospace;
+ font-size: 18px;
+ background: var(--bg-primary);
+ color: var(--text-primary);
+ min-height: 100vh;
+ padding: 2rem;
}
.container {
- max-width: 1200px;
- margin: 0 auto;
+ max-width: 1200px;
+ margin: 0 auto;
}
header {
- text-align: center;
- margin-bottom: 2rem;
+ display: flex;
+ flex-direction: column;
+ text-align: center;
+ margin-right: auto;
+ margin-left: auto;
+ margin-bottom: 2rem;
}
-.ascii-logo {
- font-family: 'JetBrains Mono', monospace;
- font-size: 0.65rem;
- line-height: 1.1;
- color: var(--accent);
- margin: 0;
- display: inline-block;
+.logo {
+ margin-left: 32px;
+ font-family: 'Ac437_Rainbow100_re_132', monospace;
+ font-size: 9rem;
+ font-weight: 700;
+ color: var(--text-primary);
+ text-align: center;
+ letter-spacing: 0.25em;
+ text-shadow: 4px 12px 12px rgb(173, 1, 124);
}
.subtitle {
- font-family: 'JetBrains Mono', monospace;
- font-size: 0.875rem;
- color: var(--text-secondary);
- letter-spacing: 0.05em;
+ font-family: 'Ac437_Rainbow100_re_132', monospace;
+ font-size: 0.875rem;
+ color: var(--text-secondary);
+ letter-spacing: 0.05em;
}
/* Carousel Navigation */
.carousel-nav {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 1.5rem;
- margin-bottom: 1rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 1.5rem;
+ margin-bottom: 1rem;
}
.nav-arrow {
- background: var(--bg-secondary);
- border: 1px solid var(--border);
- color: var(--text-primary);
- width: 40px;
- height: 40px;
- border-radius: 8px;
- cursor: pointer;
- font-size: 1rem;
- transition: all 0.2s ease;
- display: flex;
- align-items: center;
- justify-content: center;
+ background: var(--bg-secondary);
+ border: 1px solid var(--border);
+ color: var(--text-primary);
+ width: 40px;
+ height: 40px;
+ border-radius: 8px;
+ cursor: pointer;
+ font-size: 1rem;
+ transition: all 0.2s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
}
.nav-arrow:hover:not(:disabled) {
- background: var(--bg-tertiary);
- border-color: var(--accent);
- color: var(--accent);
+ background: var(--bg-tertiary);
+ border-color: var(--accent);
+ color: var(--accent);
}
.nav-arrow:disabled {
- opacity: 0.3;
- cursor: not-allowed;
+ opacity: 0.3;
+ cursor: not-allowed;
}
.date-display {
- text-align: center;
- min-width: 200px;
+ text-align: center;
+ min-width: 200px;
}
.date-label {
- display: block;
- font-size: 1.25rem;
- font-weight: 600;
- color: var(--text-primary);
+ font-family: 'Ac437_Rainbow100_re_132';
+ font-weight: 600;
+ font-size: 1.65rem;
+ font-weight: 600;
+ letter-spacing: 2px;
+ color: var(--text-primary);
}
.date-status {
- display: block;
- font-family: 'JetBrains Mono', monospace;
- font-size: 0.75rem;
- color: var(--text-secondary);
- margin-top: 0.25rem;
+ display: block;
+ font-family: 'Ac437_Rainbow100_re_132', monospace;
+ font-size: 1rem;
+ color: var(--text-secondary);
+ margin-top: 0.25rem;
}
.date-status.live {
- color: var(--green);
+ color: var(--green);
}
.date-status.live::before {
- content: '●';
- margin-right: 0.5rem;
- animation: pulse 1.5s ease-in-out infinite;
+ content: '●';
+ margin-right: 0.5rem;
+ animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
- 0%, 100% { opacity: 1; }
- 50% { opacity: 0.4; }
+
+ 0%,
+ 100% {
+ opacity: 1;
+ }
+
+ 50% {
+ opacity: 0.4;
+ }
}
/* Chart */
.chart-wrapper {
- position: relative;
- margin-bottom: 1.5rem;
+ position: relative;
+ margin-bottom: 1.5rem;
}
.chart-container {
- background: var(--bg-secondary);
- border: 1px solid var(--border);
- border-radius: 12px;
- padding: 1.5rem;
- height: 400px;
+ background: var(--bg-secondary);
+ border: 1px solid var(--border);
+ border-radius: 12px;
+ padding: 1.5rem;
+ height: 400px;
}
.tooltip {
- position: absolute;
- background: var(--bg-tertiary);
- border: 1px solid var(--border);
- border-radius: 6px;
- padding: 0.5rem 0.75rem;
- font-family: 'JetBrains Mono', monospace;
- font-size: 0.75rem;
- pointer-events: none;
- opacity: 0;
- transition: opacity 0.15s ease;
- z-index: 100;
- white-space: nowrap;
+ position: absolute;
+ background: var(--bg-tertiary);
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ padding: 0.5rem 0.75rem;
+ font-family: 'Ac437_Rainbow100_re_132', monospace;
+ font-size: 0.75rem;
+ pointer-events: none;
+ opacity: 0;
+ transition: opacity 0.15s ease;
+ z-index: 100;
+ white-space: nowrap;
}
.tooltip.visible {
- opacity: 1;
+ opacity: 1;
}
.tooltip .price {
- font-size: 0.875rem;
- font-weight: 600;
- color: var(--text-primary);
+ font-size: 0.875rem;
+ font-weight: 600;
+ color: var(--text-primary);
}
.tooltip .time {
- color: var(--text-secondary);
- margin-top: 2px;
+ color: var(--text-secondary);
+ margin-top: 2px;
}
/* Loading overlay */
.loading-overlay {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: var(--bg-secondary);
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: 1rem;
- border-radius: 12px;
- color: var(--text-secondary);
- font-family: 'JetBrains Mono', monospace;
- z-index: 50;
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: var(--bg-secondary);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 1rem;
+ border-radius: 12px;
+ color: var(--text-secondary);
+ font-family: 'Ac437_Rainbow100_re_132', monospace;
+ z-index: 50;
}
.spinner {
- width: 30px;
- height: 30px;
- border: 3px solid var(--border);
- border-top-color: var(--accent);
- border-radius: 50%;
- animation: spin 1s linear infinite;
+ width: 30px;
+ height: 30px;
+ border: 3px solid var(--border);
+ border-top-color: var(--accent);
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
}
@keyframes spin {
- to { transform: rotate(360deg); }
+ to {
+ transform: rotate(360deg);
+ }
}
/* Stats */
.stats {
- display: flex;
- justify-content: space-between;
- gap: 1rem;
- margin-bottom: 1.5rem;
+ display: flex;
+ font-size: 1.2rem;
+ justify-content: space-between;
+ gap: 1rem;
+ margin-bottom: 1.5rem;
}
.stat {
- flex: 1;
- background: var(--bg-secondary);
- border: 1px solid var(--border);
- border-radius: 8px;
- padding: 1rem;
- text-align: center;
+ flex: 1;
+ font-size: 1.2rem;
+ background: var(--bg-secondary);
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ padding: 1rem;
+ text-align: center;
}
.stat-label {
- display: block;
- font-size: 0.75rem;
- color: var(--text-secondary);
- text-transform: uppercase;
- letter-spacing: 0.05em;
- margin-bottom: 0.25rem;
+ display: block;
+ font-size: 0.75rem;
+ color: var(--text-secondary);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ margin-bottom: 0.25rem;
}
.stat-value {
- font-family: 'JetBrains Mono', monospace;
- font-size: 1.25rem;
- font-weight: 600;
+ font-family: 'Ac437_Rainbow100_re_132', monospace;
+ font-size: 1.25rem;
+ font-weight: 600;
}
-.stat-value.high { color: var(--green); }
-.stat-value.low { color: var(--red); }
-.stat-value.buy { color: var(--yellow); }
-.stat-value.sell { color: var(--orange); }
+.stat-value.high {
+ color: var(--green);
+}
+
+.stat-value.low {
+ color: var(--red);
+}
+
+.stat-value.buy {
+ color: var(--yellow);
+}
+
+.stat-value.sell {
+ color: var(--orange);
+}
/* Day indicators */
.day-indicators {
- display: flex;
- justify-content: center;
- gap: 0.5rem;
- margin-bottom: 1rem;
+ display: flex;
+ justify-content: center;
+ gap: 0.5rem;
+ margin-bottom: 1rem;
}
.day-dot {
- width: 8px;
- height: 8px;
- border-radius: 50%;
- background: var(--border);
- cursor: pointer;
- transition: all 0.2s ease;
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: var(--border);
+ cursor: pointer;
+ transition: all 0.2s ease;
}
.day-dot:hover {
- background: var(--text-secondary);
+ background: var(--text-secondary);
}
.day-dot.active {
- background: var(--accent);
- box-shadow: 0 0 8px var(--accent);
+ background: var(--accent);
+ box-shadow: 0 0 8px var(--accent);
}
/* Legend */
.legend {
- display: flex;
- justify-content: center;
- gap: 2rem;
- font-size: 0.875rem;
- color: var(--text-secondary);
+ display: flex;
+ justify-content: center;
+ gap: 2rem;
+ font-size: 0.875rem;
+ color: var(--text-secondary);
}
.legend-item {
- display: flex;
- align-items: center;
- gap: 0.5rem;
+ display: flex;
+ align-items: center;
+ font-size: 1rem;
+ gap: 0.5rem;
}
.dot {
- width: 10px;
- height: 10px;
- border-radius: 50%;
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
}
.buy-dot {
- background: var(--yellow);
- box-shadow: 0 0 8px var(--yellow);
+ background: var(--yellow);
+ box-shadow: 0 0 8px var(--yellow);
}
.sell-dot {
- background: var(--orange);
- box-shadow: 0 0 8px var(--orange);
+ background: var(--orange);
+ box-shadow: 0 0 8px var(--orange);
}
/* Connection status */
.connection-status {
- position: fixed;
- bottom: 1rem;
- right: 1rem;
- display: flex;
- align-items: center;
- gap: 0.5rem;
- background: var(--bg-secondary);
- border: 1px solid var(--border);
- border-radius: 6px;
- padding: 0.5rem 0.75rem;
- font-family: 'JetBrains Mono', monospace;
- font-size: 0.75rem;
- color: var(--text-secondary);
+ position: fixed;
+ bottom: 1rem;
+ right: 1rem;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ background: var(--bg-secondary);
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ padding: 0.5rem 0.75rem;
+ font-family: 'Ac437_Rainbow100_re_132', monospace;
+ font-size: 0.75rem;
+ color: var(--text-secondary);
}
.connection-status .status-dot {
- width: 8px;
- height: 8px;
- border-radius: 50%;
- background: var(--text-muted);
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: var(--text-muted);
}
.connection-status.connected .status-dot {
- background: var(--green);
- box-shadow: 0 0 6px var(--green);
+ background: var(--green);
+ box-shadow: 0 0 6px var(--green);
}
.connection-status.waiting .status-dot {
- background: var(--yellow);
- animation: pulse 1.5s ease-in-out infinite;
+ background: var(--yellow);
+ animation: pulse 1.5s ease-in-out infinite;
}
/* Responsive */
@media (max-width: 768px) {
- body {
- padding: 1rem;
- }
-
- h1 {
- font-size: 1.75rem;
- }
-
- .stats {
- flex-wrap: wrap;
- }
-
- .stat {
- flex: 1 1 45%;
- }
-
- .chart-container {
- height: 300px;
- }
+ body {
+ padding: 1rem;
+ }
+
+ h1 {
+ font-size: 1.75rem;
+ }
+
+ .stats {
+ flex-wrap: wrap;
+ }
+
+ .stat {
+ flex: 1 1 45%;
+ }
+
+ .chart-container {
+ height: 300px;
+ }
}
/* Grid Levels Display */
.grid-levels {
- margin-top: 1.5rem;
- padding: 1rem;
- background: var(--bg-secondary);
- border: 1px solid var(--border);
- border-radius: 8px;
+ margin-top: 1.5rem;
+ padding: 1rem;
+ background: var(--bg-secondary);
+ border: 1px solid var(--border);
+ border-radius: 8px;
}
.grid-levels-header {
- text-align: center;
- font-size: 0.875rem;
- font-weight: 600;
- color: var(--text-secondary);
- margin-bottom: 1rem;
- text-transform: uppercase;
- letter-spacing: 0.05em;
+ text-align: center;
+ font-size: 1.2rem;
+ font-weight: 600;
+ color: var(--text-secondary);
+ margin-bottom: 1rem;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
}
.grid-levels-container {
- display: flex;
- justify-content: center;
- gap: 2rem;
+ display: flex;
+ justify-content: center;
+ gap: 2rem;
}
.grid-column {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 0.375rem;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 0.375rem;
}
.grid-column-header {
- font-size: 0.75rem;
- font-weight: 600;
- text-transform: uppercase;
- letter-spacing: 0.05em;
- margin-bottom: 0.5rem;
- padding: 0.25rem 0.75rem;
- border-radius: 4px;
+ font-size: 1.2rem;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ margin-bottom: 0.5rem;
+ padding: 0.25rem 0.75rem;
+ border-radius: 4px;
}
.grid-column-header.buy {
- color: var(--yellow);
- background: rgba(240, 192, 0, 0.1);
+ font-size: 1.2rem;
+ color: var(--yellow);
+ background: rgba(240, 192, 0, 0.1);
}
.grid-column-header.sell {
- color: var(--orange);
- background: rgba(219, 109, 40, 0.1);
+ font-size: 1.2rem;
+ color: var(--orange);
+ background: rgba(219, 109, 40, 0.1);
}
.grid-level {
- font-family: 'JetBrains Mono', monospace;
- font-size: 0.8rem;
- padding: 0.25rem 0.75rem;
- border-radius: 4px;
- min-width: 100px;
- text-align: center;
+ font-family: 'Ac437_Rainbow100_re_132', monospace;
+ font-size: 1.2rem;
+ padding: 0.25rem 0.75rem;
+ border-radius: 4px;
+ min-width: 100px;
+ text-align: center;
}
.grid-level.buy {
- color: var(--yellow);
- background: rgba(240, 192, 0, 0.08);
- border: 1px solid rgba(240, 192, 0, 0.2);
+ color: var(--yellow);
+ font-size: 1.2rem;
+ background: rgba(240, 192, 0, 0.08);
+ border: 1px solid rgba(240, 192, 0, 0.2);
}
.grid-level.sell {
- color: var(--orange);
- background: rgba(219, 109, 40, 0.08);
- border: 1px solid rgba(219, 109, 40, 0.2);
+ color: var(--orange);
+ background: rgba(219, 109, 40, 0.08);
+ border: 1px solid rgba(219, 109, 40, 0.2);
}
.grid-level.filled {
- opacity: 0.5;
- text-decoration: line-through;
+ opacity: 0.5;
+ text-decoration: line-through;
}
/* Auth Pages */
.auth-container {
- min-height: 100vh;
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 2rem;
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 2rem;
}
.auth-card {
- background: var(--bg-secondary);
- border: 1px solid var(--border);
- border-radius: 12px;
- padding: 2.5rem;
- width: 100%;
- max-width: 420px;
- text-align: center;
+ background: var(--bg-secondary);
+ border: 1px solid var(--border);
+ border-radius: 12px;
+ padding: 2.5rem;
+ width: 100%;
+ max-width: 420px;
+ text-align: center;
}
-.auth-card .ascii-logo {
- margin-bottom: 1.5rem;
+.auth-card .logo {
+ margin-bottom: 1.5rem;
}
.auth-card h2 {
- font-size: 1.5rem;
- font-weight: 600;
- color: var(--text-primary);
- margin-bottom: 1.5rem;
+ font-size: 1.5rem;
+ font-weight: 600;
+ color: var(--text-primary);
+ margin-bottom: 1.5rem;
}
.auth-form {
- display: flex;
- flex-direction: column;
- gap: 1rem;
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
}
.form-group {
- text-align: left;
+ text-align: left;
}
.form-group label {
- display: block;
- font-size: 0.875rem;
- color: var(--text-secondary);
- margin-bottom: 0.5rem;
+ display: block;
+ font-size: 0.875rem;
+ color: var(--text-secondary);
+ margin-bottom: 0.5rem;
}
.form-group input {
- width: 100%;
- padding: 0.75rem 1rem;
- background: var(--bg-primary);
- border: 1px solid var(--border);
- border-radius: 8px;
- color: var(--text-primary);
- font-size: 1rem;
- font-family: inherit;
- transition: border-color 0.2s, box-shadow 0.2s;
+ width: 100%;
+ padding: 0.75rem 1rem;
+ background: var(--bg-primary);
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ color: var(--text-primary);
+ font-size: 1rem;
+ font-family: inherit;
+ transition: border-color 0.2s, box-shadow 0.2s;
}
.form-group input:focus {
- outline: none;
- border-color: var(--accent);
- box-shadow: 0 0 0 3px rgba(88, 166, 255, 0.15);
+ outline: none;
+ border-color: var(--accent);
+ box-shadow: 0 0 0 3px rgba(88, 166, 255, 0.15);
}
.form-group input::placeholder {
- color: var(--text-muted);
+ color: var(--text-muted);
}
.auth-btn {
- width: 100%;
- padding: 0.875rem 1.5rem;
- background: var(--accent);
- color: var(--bg-primary);
- border: none;
- border-radius: 8px;
- font-size: 1rem;
- font-weight: 600;
- font-family: inherit;
- cursor: pointer;
- transition: opacity 0.2s, transform 0.1s;
- margin-top: 0.5rem;
+ width: 100%;
+ padding: 0.875rem 1.5rem;
+ background: var(--accent);
+ color: var(--bg-primary);
+ border: none;
+ border-radius: 8px;
+ font-size: 1rem;
+ font-weight: 600;
+ font-family: inherit;
+ cursor: pointer;
+ transition: opacity 0.2s, transform 0.1s;
+ margin-top: 0.5rem;
}
.auth-btn:hover:not(:disabled) {
- opacity: 0.9;
+ opacity: 0.9;
}
.auth-btn:active:not(:disabled) {
- transform: scale(0.98);
+ transform: scale(0.98);
}
.auth-btn:disabled {
- opacity: 0.6;
- cursor: not-allowed;
+ opacity: 0.6;
+ cursor: not-allowed;
}
.auth-error {
- background: var(--red-fill);
- border: 1px solid var(--red);
- color: var(--red);
- padding: 0.75rem 1rem;
- border-radius: 8px;
- font-size: 0.875rem;
- margin-bottom: 1rem;
+ background: var(--red-fill);
+ border: 1px solid var(--red);
+ color: var(--red);
+ padding: 0.75rem 1rem;
+ border-radius: 8px;
+ font-size: 0.875rem;
+ margin-bottom: 1rem;
}
.auth-success {
- background: var(--green-fill);
- border: 1px solid var(--green);
- color: var(--green);
- padding: 0.75rem 1rem;
- border-radius: 8px;
- font-size: 0.875rem;
- margin-bottom: 1rem;
+ background: var(--green-fill);
+ border: 1px solid var(--green);
+ color: var(--green);
+ padding: 0.75rem 1rem;
+ border-radius: 8px;
+ font-size: 0.875rem;
+ margin-bottom: 1rem;
}
.auth-link {
- margin-top: 1.5rem;
- color: var(--text-secondary);
- font-size: 0.875rem;
+ margin-top: 1.5rem;
+ color: var(--text-secondary);
+ font-size: 0.875rem;
}
.auth-link a {
- color: var(--accent);
- text-decoration: none;
- font-weight: 600;
+ color: var(--accent);
+ text-decoration: none;
+ font-weight: 600;
}
.auth-link a:hover {
- text-decoration: underline;
+ text-decoration: underline;
}
/* Waitlist Message */
.waitlist-message {
- background: var(--bg-tertiary);
- border: 1px solid var(--border);
- border-radius: 8px;
- padding: 1.5rem;
- margin: 1rem 0;
- text-align: left;
- line-height: 1.6;
+ background: var(--bg-tertiary);
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ padding: 1.5rem;
+ margin: 1rem 0;
+ text-align: left;
+ line-height: 1.6;
}
.waitlist-message p {
- color: var(--text-secondary);
- margin-bottom: 1rem;
+ color: var(--text-secondary);
+ margin-bottom: 1rem;
}
.waitlist-message .thank-you {
- color: var(--accent);
- font-weight: 600;
- text-align: center;
- margin-bottom: 0;
+ color: var(--accent);
+ font-weight: 600;
+ text-align: center;
+ margin-bottom: 0;
}
/* Logout Button */
.logout-btn {
- position: absolute;
- top: 1rem;
- right: 1rem;
- padding: 0.5rem 1rem;
- background: transparent;
- border: 1px solid var(--border);
- border-radius: 6px;
- color: var(--text-secondary);
- font-size: 0.875rem;
- font-family: inherit;
- cursor: pointer;
- transition: border-color 0.2s, color 0.2s;
+ height: 40px;
+ width: 110px;
+ font-size: 1.2rem;
+ font-family: Ac437_Rainbow100_re_132;
+ position: absolute;
+ top: 1rem;
+ right: 1rem;
+ padding: 0.5rem 1rem;
+ background: transparent;
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ color: var(--text-secondary);
+ font-family: inherit;
+ cursor: pointer;
+ transition: border-color 0.2s, color 0.2s;
}
.logout-btn:hover {
- border-color: var(--red);
- color: var(--red);
+ border-color: var(--red);
+ color: var(--red);
}
header {
- position: relative;
-}
+ position: relative;
+}
\ No newline at end of file
diff --git a/trahn-trade-frontend/src/App.jsx b/trahn-trade-frontend/src/App.jsx
index efc21df..c8c95f4 100644
--- a/trahn-trade-frontend/src/App.jsx
+++ b/trahn-trade-frontend/src/App.jsx
@@ -8,7 +8,6 @@ import { Login } from './pages/Login';
import { Signup } from './pages/Signup';
import './App.css';
-// Create React Query client
const queryClient = new QueryClient({
defaultOptions: {
queries: {
@@ -20,7 +19,6 @@ const queryClient = new QueryClient({
},
});
-// Redirect authenticated users away from auth pages
function PublicRoute({ children }) {
const { currentUser } = useAuth();
diff --git a/trahn-trade-frontend/src/api/tradingApi.js b/trahn-trade-frontend/src/api/tradingApi.js
index f1d1ba9..31a0ed5 100644
--- a/trahn-trade-frontend/src/api/tradingApi.js
+++ b/trahn-trade-frontend/src/api/tradingApi.js
@@ -1,81 +1,54 @@
-/**
- * Trading API Client
- * Communicates with backend REST API
- */
-
-const API_BASE = 'http://localhost:3001/api';
+const API_BASE = 'http://localhost:3001/v1';
export const tradingApi = {
- /**
- * Get current trading day prices
- */
- getCurrentPrices: async () => {
- const response = await fetch(`${API_BASE}/prices/today`);
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
- return response.json();
- },
- /**
- * Get current trading day trades
- */
- getCurrentTrades: async () => {
- const response = await fetch(`${API_BASE}/trades/today`);
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
- return response.json();
- },
+ getCurrentPrices: async () => {
+ const response = await fetch(`${API_BASE}/prices/today`);
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
+ return response.json();
+ },
- /**
- * Get current grid state
- */
- getCurrentGrid: async () => {
- const response = await fetch(`${API_BASE}/grid/current`);
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
- return response.json();
- },
+ getCurrentTrades: async () => {
+ const response = await fetch(`${API_BASE}/trades/today`);
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
+ return response.json();
+ },
- /**
- * Get latest support/resistance levels
- */
- getLatestSR: async () => {
- const response = await fetch(`${API_BASE}/support-resistance/latest`);
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
- return response.json();
- },
+ getCurrentGrid: async () => {
+ const response = await fetch(`${API_BASE}/grid/current`);
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
+ return response.json();
+ },
- /**
- * Get available trading days
- */
- getAvailableDays: async () => {
- const response = await fetch(`${API_BASE}/prices/days`);
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
- return response.json();
- },
- /**
- * Get historical prices for a specific day
- */
- getPricesByDay: async (date) => {
- const response = await fetch(`${API_BASE}/prices/day/${date}`);
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
- return response.json();
- },
+ getLatestSR: async () => {
+ const response = await fetch(`${API_BASE}/support-resistance/latest`);
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
+ return response.json();
+ },
- /**
- * Get historical trades for a specific day
- */
- getTradesByDay: async (date) => {
- const response = await fetch(`${API_BASE}/trades/day/${date}`);
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
- return response.json();
- },
+ getAvailableDays: async () => {
+ const response = await fetch(`${API_BASE}/prices/days`);
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
+ return response.json();
+ },
- /**
- * Get trade statistics
- */
- getTradeStats: async () => {
- const response = await fetch(`${API_BASE}/trades/stats`);
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
- return response.json();
- },
+ getPricesByDay: async (date) => {
+ const response = await fetch(`${API_BASE}/prices/day/${date}`);
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
+ return response.json();
+ },
+
+ getTradesByDay: async (date) => {
+ const response = await fetch(`${API_BASE}/trades/day/${date}`);
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
+ return response.json();
+ },
+
+ getTradeStats: async () => {
+ const response = await fetch(`${API_BASE}/trades/stats`);
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
+ return response.json();
+ },
};
diff --git a/trahn-trade-frontend/src/auth/firebase.js b/trahn-trade-frontend/src/auth/firebase.js
index 03ee791..37726e9 100644
--- a/trahn-trade-frontend/src/auth/firebase.js
+++ b/trahn-trade-frontend/src/auth/firebase.js
@@ -7,7 +7,6 @@ const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);
export const db = getFirestore(app);
-// Add email to waitlist collection
export async function addToWaitlist(email) {
try {
await addDoc(collection(db, 'trahn-trade-signups'), {
diff --git a/trahn-trade-frontend/src/components/Chart.jsx b/trahn-trade-frontend/src/components/Chart.jsx
index 649fa97..6f4613a 100644
--- a/trahn-trade-frontend/src/components/Chart.jsx
+++ b/trahn-trade-frontend/src/components/Chart.jsx
@@ -1,350 +1,333 @@
import { useRef, useEffect, useState } from 'react';
const COLORS = {
- line: '#58a6ff',
- green: '#3fb950',
- greenFill: 'rgba(63, 185, 80, 0.12)',
- red: '#f85149',
- redFill: 'rgba(248, 81, 73, 0.12)',
- grid: '#21262d',
- text: '#8b949e',
- textMuted: '#484f58',
- baseline: '#30363d',
- buy: '#f0c000',
- sell: '#db6d28',
- bg: '#0d1117',
+ line: '#58a6ff',
+ green: '#3fb950',
+ greenFill: 'rgba(63, 185, 80, 0.12)',
+ red: '#f85149',
+ redFill: 'rgba(248, 81, 73, 0.12)',
+ grid: '#21262d',
+ text: '#8b949e',
+ textMuted: '#484f58',
+ baseline: '#30363d',
+ buy: '#f0c000',
+ sell: '#db6d28',
+ bg: '#0d1117',
};
const PADDING = { top: 20, right: 80, bottom: 40, left: 20 };
export function Chart({ prices, trades }) {
- const canvasRef = useRef(null);
- const containerRef = useRef(null);
- const [tooltip, setTooltip] = useState(null);
- const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
+ const canvasRef = useRef(null);
+ const containerRef = useRef(null);
+ const [tooltip, setTooltip] = useState(null);
+ const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
- // Handle resize
- useEffect(() => {
- const updateDimensions = () => {
- if (containerRef.current) {
- const rect = containerRef.current.getBoundingClientRect();
- setDimensions({ width: rect.width, height: rect.height });
- }
- };
-
- updateDimensions();
- window.addEventListener('resize', updateDimensions);
- return () => window.removeEventListener('resize', updateDimensions);
- }, []);
-
- // Draw chart
- useEffect(() => {
- const canvas = canvasRef.current;
- if (!canvas || dimensions.width === 0) return;
-
- const ctx = canvas.getContext('2d');
- const dpr = window.devicePixelRatio || 1;
-
- canvas.width = dimensions.width * dpr;
- canvas.height = dimensions.height * dpr;
- ctx.scale(dpr, dpr);
-
- const width = dimensions.width;
- const height = dimensions.height;
- const chartWidth = width - PADDING.left - PADDING.right;
- const chartHeight = height - PADDING.top - PADDING.bottom;
-
- // Clear
- ctx.clearRect(0, 0, width, height);
-
- if (!prices || prices.length === 0) {
- ctx.fillStyle = COLORS.textMuted;
- ctx.font = '14px JetBrains Mono, monospace';
- ctx.textAlign = 'center';
- ctx.fillText('No data for this day', width / 2, height / 2);
- return;
- }
-
- // Calculate bounds
- const priceValues = prices.map(d => d.p || d.price);
- const times = prices.map(d => d.t || d.timestamp);
-
- let minPrice = Math.min(...priceValues);
- let maxPrice = Math.max(...priceValues);
- const minTime = Math.min(...times);
- const maxTime = Math.max(...times);
-
- const pricePadding = (maxPrice - minPrice) * 0.05 || 10;
- minPrice -= pricePadding;
- maxPrice += pricePadding;
-
- const baselinePrice = priceValues[0];
-
- // Coordinate converters
- const priceToY = (price) => {
- const range = maxPrice - minPrice;
- const normalized = (price - minPrice) / range;
- return PADDING.top + chartHeight * (1 - normalized);
- };
-
- const timeToX = (timestamp) => {
- const range = maxTime - minTime;
- const normalized = (timestamp - minTime) / range;
- return PADDING.left + chartWidth * normalized;
- };
-
- // Draw grid
- ctx.strokeStyle = COLORS.grid;
- ctx.lineWidth = 1;
-
- const priceStep = calculateNiceStep(maxPrice - minPrice, 5);
- const startPrice = Math.ceil(minPrice / priceStep) * priceStep;
-
- for (let price = startPrice; price <= maxPrice; price += priceStep) {
- const y = priceToY(price);
- ctx.beginPath();
- ctx.moveTo(PADDING.left, y);
- ctx.lineTo(width - PADDING.right, y);
- ctx.stroke();
- }
-
- // Draw baseline
- const baselineY = priceToY(baselinePrice);
- ctx.strokeStyle = COLORS.baseline;
- ctx.setLineDash([4, 4]);
- ctx.beginPath();
- ctx.moveTo(PADDING.left, baselineY);
- ctx.lineTo(width - PADDING.right, baselineY);
- ctx.stroke();
- ctx.setLineDash([]);
-
- // Draw gradient fill
- if (prices.length >= 2) {
- ctx.beginPath();
- ctx.moveTo(timeToX(times[0]), baselineY);
- ctx.lineTo(timeToX(times[0]), priceToY(priceValues[0]));
-
- for (let i = 1; i < prices.length; i++) {
- ctx.lineTo(timeToX(times[i]), priceToY(priceValues[i]));
- }
-
- ctx.lineTo(timeToX(times[times.length - 1]), baselineY);
- ctx.closePath();
-
- const gradient = ctx.createLinearGradient(0, PADDING.top, 0, height - PADDING.bottom);
- gradient.addColorStop(0, COLORS.greenFill);
- gradient.addColorStop(0.5, 'rgba(0,0,0,0)');
- gradient.addColorStop(1, COLORS.redFill);
- ctx.fillStyle = gradient;
- ctx.fill();
- }
-
- // Draw price line
- if (prices.length >= 2) {
- ctx.beginPath();
- ctx.strokeStyle = COLORS.line;
- ctx.lineWidth = 2;
- ctx.lineJoin = 'round';
- ctx.lineCap = 'round';
-
- ctx.moveTo(timeToX(times[0]), priceToY(priceValues[0]));
- for (let i = 1; i < prices.length; i++) {
- ctx.lineTo(timeToX(times[i]), priceToY(priceValues[i]));
- }
- ctx.stroke();
- }
-
- // Draw Y axis labels
- ctx.fillStyle = COLORS.text;
- ctx.font = '11px JetBrains Mono, monospace';
- ctx.textAlign = 'left';
-
- for (let price = startPrice; price <= maxPrice; price += priceStep) {
- const y = priceToY(price);
- ctx.fillText(formatPrice(price), width - PADDING.right + 8, y + 4);
- }
-
- // Draw X axis labels
- ctx.textAlign = 'center';
- const hourMs = 60 * 60 * 1000;
- const rangeMs = maxTime - minTime;
- let stepMs = hourMs * 4;
- if (rangeMs < hourMs * 6) stepMs = hourMs;
- if (rangeMs < hourMs * 2) stepMs = hourMs / 2;
-
- let current = Math.ceil(minTime / stepMs) * stepMs;
- while (current <= maxTime) {
- const x = timeToX(current);
- if (x >= PADDING.left && x <= width - PADDING.right) {
- const date = new Date(current);
- const label = date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false });
- ctx.fillText(label, x, height - PADDING.bottom + 20);
- }
- current += stepMs;
- }
-
- // Draw trades
- for (const trade of (trades || [])) {
- const x = timeToX(trade.t || trade.timestamp);
- const y = priceToY(trade.price);
-
- if (x < PADDING.left || x > width - PADDING.right) continue;
-
- const color = trade.side === 'buy' ? COLORS.buy : COLORS.sell;
- const radius = 7;
-
- // Glow
- ctx.beginPath();
- ctx.arc(x, y, radius + 4, 0, Math.PI * 2);
- ctx.fillStyle = color + '33';
- ctx.fill();
-
- // Outer
- ctx.beginPath();
- ctx.arc(x, y, radius, 0, Math.PI * 2);
- ctx.fillStyle = color;
- ctx.fill();
-
- // Inner
- ctx.beginPath();
- ctx.arc(x, y, radius - 3, 0, Math.PI * 2);
- ctx.fillStyle = COLORS.bg;
- ctx.fill();
- }
-
- // Draw current price tag
- if (prices.length > 0) {
- const lastPrice = priceValues[priceValues.length - 1];
- const y = priceToY(lastPrice);
- const x = width - PADDING.right;
-
- const isUp = lastPrice >= baselinePrice;
- const color = isUp ? COLORS.green : COLORS.red;
-
- ctx.fillStyle = color;
- roundRect(ctx, x + 4, y - 11, 70, 22, 4);
- ctx.fill();
-
- ctx.fillStyle = '#fff';
- ctx.font = 'bold 11px JetBrains Mono, monospace';
- ctx.textAlign = 'left';
- ctx.fillText(formatPrice(lastPrice), x + 10, y + 4);
- }
-
- }, [prices, trades, dimensions]);
-
- // Mouse handling for tooltip
- const handleMouseMove = (e) => {
- if (!prices || prices.length === 0) {
- setTooltip(null);
- return;
- }
-
- const rect = canvasRef.current.getBoundingClientRect();
- const x = e.clientX - rect.left;
- const y = e.clientY - rect.top;
-
- const width = dimensions.width;
- const height = dimensions.height;
- const chartWidth = width - PADDING.left - PADDING.right;
-
- if (x < PADDING.left || x > width - PADDING.right ||
- y < PADDING.top || y > height - PADDING.bottom) {
- setTooltip(null);
- return;
- }
-
- const times = prices.map(d => d.t || d.timestamp);
- const minTime = Math.min(...times);
- const maxTime = Math.max(...times);
-
- const normalized = (x - PADDING.left) / chartWidth;
- const timestamp = minTime + normalized * (maxTime - minTime);
-
- let nearestPoint = null;
- let nearestDistance = Infinity;
-
- for (const point of prices) {
- const t = point.t || point.timestamp;
- const distance = Math.abs(t - timestamp);
- if (distance < nearestDistance) {
- nearestDistance = distance;
- nearestPoint = point;
- }
- }
-
- if (nearestPoint) {
- setTooltip({
- x: e.clientX - rect.left + 15,
- y: e.clientY - rect.top - 10,
- price: nearestPoint.p || nearestPoint.price,
- timestamp: nearestPoint.t || nearestPoint.timestamp,
- });
- }
+ useEffect(() => {
+ const updateDimensions = () => {
+ if (containerRef.current) {
+ const rect = containerRef.current.getBoundingClientRect();
+ setDimensions({ width: rect.width, height: rect.height });
+ }
};
- return (
-
-