Lesss gooo claude
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
import { getLockState, setLockState } from '@/background/lock-state';
|
||||
import type { NexaMessage } from '@/shared/messaging';
|
||||
|
||||
export default defineBackground(() => {
|
||||
browser.runtime.onMessage.addListener((message: NexaMessage, _sender, sendResponse) => {
|
||||
switch (message?.type) {
|
||||
case 'nexa:get-lock-state':
|
||||
getLockState().then(sendResponse);
|
||||
return true; // keep the message channel open for the async response
|
||||
|
||||
case 'nexa:set-lock-state':
|
||||
setLockState(message.state).then(() => sendResponse({ ok: true }));
|
||||
return true;
|
||||
|
||||
case 'nexa:open-popup':
|
||||
// Best-effort: not all browsers/contexts allow programmatic popup
|
||||
// opening outside a direct user gesture on the action icon.
|
||||
browser.action.openPopup().catch(() => undefined);
|
||||
return false;
|
||||
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import { initContentScript } from '@/content-scripts/content-index';
|
||||
|
||||
export default defineContentScript({
|
||||
matches: ['https://axiom.trade/*'],
|
||||
runAt: 'document_idle',
|
||||
main() {
|
||||
initContentScript();
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Nexa</title>
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="./main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,101 @@
|
||||
import { browser } from 'wxt/browser';
|
||||
import { DEFAULT_LOCK_STATE, type LockScope, type LockState } from '@/shared/types';
|
||||
import type { NexaMessage } from '@/shared/messaging';
|
||||
|
||||
const app = document.getElementById('app');
|
||||
if (!app) throw new Error('Popup root element #app not found.');
|
||||
|
||||
app.innerHTML = `
|
||||
<header>
|
||||
<h1>Nexa</h1>
|
||||
<p class="subtitle">Trading companion</p>
|
||||
</header>
|
||||
|
||||
<section class="status" id="status"></section>
|
||||
|
||||
<section class="dev-controls">
|
||||
<h2>Dev mock controls</h2>
|
||||
<p class="hint">No backend connected yet — use this to simulate a lock signal.</p>
|
||||
|
||||
<label class="field">
|
||||
<span>Locked</span>
|
||||
<input type="checkbox" id="locked-toggle" />
|
||||
</label>
|
||||
|
||||
<label class="field">
|
||||
<span>Scope</span>
|
||||
<select id="scope-select">
|
||||
<option value="buy-only">buy-only</option>
|
||||
<option value="full-block">full-block</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="field">
|
||||
<span>Reason</span>
|
||||
<input type="text" id="reason-input" placeholder="e.g. -$62 loss crossed your $50 threshold" />
|
||||
</label>
|
||||
</section>
|
||||
|
||||
<a class="thresholds-link" id="thresholds-link" href="#" title="Threshold settings are not part of this milestone">
|
||||
Adjust loss thresholds →
|
||||
</a>
|
||||
`;
|
||||
|
||||
const statusEl = document.getElementById('status') as HTMLElement;
|
||||
const lockedToggle = document.getElementById('locked-toggle') as HTMLInputElement;
|
||||
const scopeSelect = document.getElementById('scope-select') as HTMLSelectElement;
|
||||
const reasonInput = document.getElementById('reason-input') as HTMLInputElement;
|
||||
|
||||
function escapeHtml(value: string): string {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = value;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function renderStatus(state: LockState): void {
|
||||
statusEl.innerHTML = state.locked
|
||||
? `<div class="badge badge-locked">🔒 Locked</div>
|
||||
<p class="reason">${escapeHtml(state.reason ?? 'No reason provided.')}</p>
|
||||
<p class="scope">Scope: ${escapeHtml(state.scope)}</p>`
|
||||
: `<div class="badge badge-unlocked">Unlocked</div>`;
|
||||
}
|
||||
|
||||
function syncFormFromState(state: LockState): void {
|
||||
lockedToggle.checked = state.locked;
|
||||
scopeSelect.value = state.scope;
|
||||
reasonInput.value = state.reason ?? '';
|
||||
}
|
||||
|
||||
async function loadState(): Promise<LockState> {
|
||||
const message: NexaMessage = { type: 'nexa:get-lock-state' };
|
||||
const state = (await browser.runtime.sendMessage(message)) as LockState | undefined;
|
||||
return state ?? DEFAULT_LOCK_STATE;
|
||||
}
|
||||
|
||||
async function pushState(): Promise<void> {
|
||||
const next: LockState = {
|
||||
locked: lockedToggle.checked,
|
||||
scope: scopeSelect.value as LockScope,
|
||||
reason: reasonInput.value.trim() || undefined,
|
||||
lockedAt: lockedToggle.checked ? Date.now() : undefined,
|
||||
};
|
||||
const message: NexaMessage = { type: 'nexa:set-lock-state', state: next };
|
||||
await browser.runtime.sendMessage(message);
|
||||
renderStatus(next);
|
||||
}
|
||||
|
||||
for (const el of [lockedToggle, scopeSelect, reasonInput]) {
|
||||
el.addEventListener('change', () => void pushState());
|
||||
}
|
||||
|
||||
loadState().then((state) => {
|
||||
syncFormFromState(state);
|
||||
renderStatus(state);
|
||||
});
|
||||
|
||||
browser.runtime.onMessage.addListener((message: NexaMessage) => {
|
||||
if (message?.type === 'nexa:lock-state-changed') {
|
||||
syncFormFromState(message.state);
|
||||
renderStatus(message.state);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,91 @@
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
}
|
||||
|
||||
body {
|
||||
width: 280px;
|
||||
margin: 0;
|
||||
padding: 16px;
|
||||
font: 13px/1.4 -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
header .subtitle {
|
||||
margin: 2px 0 12px;
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
.status {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 3px 8px;
|
||||
border-radius: 999px;
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.badge-locked {
|
||||
background: rgba(220, 38, 38, 0.15);
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.badge-unlocked {
|
||||
background: rgba(22, 163, 74, 0.15);
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.status .reason {
|
||||
margin: 8px 0 2px;
|
||||
}
|
||||
|
||||
.status .scope {
|
||||
margin: 0;
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
.dev-controls {
|
||||
border-top: 1px solid rgba(128, 128, 128, 0.25);
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
.dev-controls h2 {
|
||||
margin: 0 0 4px;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
.dev-controls .hint {
|
||||
margin: 0 0 10px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.field input[type='text'] {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.thresholds-link {
|
||||
display: block;
|
||||
margin-top: 14px;
|
||||
color: inherit;
|
||||
opacity: 0.5;
|
||||
text-decoration: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
Reference in New Issue
Block a user