Files
the-other-dude/frontend/src/routes/_authenticated/about.tsx
Jason Staack 4924580a4b feat(ui): polish remaining pages with Deep Space tokens
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 18:40:48 -05:00

626 lines
17 KiB
TypeScript

import { createFileRoute } from '@tanstack/react-router'
import { useEffect, useRef, useState } from 'react'
import { RugLogo } from '@/components/brand/RugLogo'
export const Route = createFileRoute('/_authenticated/about')({
component: AboutPage,
})
// ── Minimal QR Code Generator (no dependencies) ─────────────────────────────
// Implements a basic QR encoder for alphanumeric/byte mode, version 1-4
// Sufficient for encoding a Bitcoin address (~62 chars)
const BTC_ADDRESS = 'bc1qfw6pmyc96vrlkpc0rgun0s7xy4sqhx7a2xurkf'
// Generate QR matrix for a given string using a minimal implementation
function generateQRMatrix(data: string): boolean[][] {
// Use the canvas-based approach with a simple encoding
// For a self-contained solution, we'll generate a basic QR-like pattern
// This is a real QR encoder for small payloads
const codewords = encodeData(data)
const version = getMinVersion(data.length)
const size = 17 + version * 4
const matrix: (boolean | null)[][] = Array.from({ length: size }, () =>
Array(size).fill(null)
)
// Place finder patterns
placeFinder(matrix, 0, 0)
placeFinder(matrix, 0, size - 7)
placeFinder(matrix, size - 7, 0)
// Place timing patterns
for (let i = 8; i < size - 8; i++) {
matrix[6][i] = i % 2 === 0
matrix[i][6] = i % 2 === 0
}
// Place alignment pattern for version >= 2
if (version >= 2) {
const pos = alignmentPositions[version]
if (pos) {
for (const r of pos) {
for (const c of pos) {
if (matrix[r]?.[c] === null) {
placeAlignment(matrix, r, c)
}
}
}
}
}
// Dark module
matrix[size - 8][8] = true
// Reserve format info
for (let i = 0; i < 8; i++) {
if (matrix[8][i] === null) matrix[8][i] = false
if (matrix[i][8] === null) matrix[i][8] = false
if (matrix[8][size - 1 - i] === null) matrix[8][size - 1 - i] = false
if (matrix[size - 1 - i][8] === null) matrix[size - 1 - i][8] = false
}
if (matrix[8][8] === null) matrix[8][8] = false
// Place data bits
placeData(matrix, codewords, size)
// Apply mask (mask 0: (row + col) % 2 === 0)
const result: boolean[][] = matrix.map((row, r) =>
row.map((cell, c) => {
if (cell === null) return false
const isData = !isReserved(matrix, r, c, size, version)
if (isData) {
const mask = (r + c) % 2 === 0
return mask ? !cell : (cell as boolean)
}
return cell as boolean
})
)
// Place format info for mask 0, error correction L
placeFormatInfo(result, size)
return result
}
function getMinVersion(len: number): number {
// Byte mode capacities for EC level L
if (len <= 17) return 1
if (len <= 32) return 2
if (len <= 53) return 3
return 4
}
const alignmentPositions: Record<number, number[]> = {
2: [6, 18],
3: [6, 22],
4: [6, 26],
}
function placeFinder(matrix: (boolean | null)[][], row: number, col: number) {
for (let r = -1; r <= 7; r++) {
for (let c = -1; c <= 7; c++) {
const mr = row + r
const mc = col + c
if (mr < 0 || mc < 0 || mr >= matrix.length || mc >= matrix.length)
continue
if (r === -1 || r === 7 || c === -1 || c === 7) {
matrix[mr][mc] = false // separator
} else if (
r === 0 ||
r === 6 ||
c === 0 ||
c === 6 ||
(r >= 2 && r <= 4 && c >= 2 && c <= 4)
) {
matrix[mr][mc] = true
} else {
matrix[mr][mc] = false
}
}
}
}
function placeAlignment(
matrix: (boolean | null)[][],
row: number,
col: number
) {
for (let r = -2; r <= 2; r++) {
for (let c = -2; c <= 2; c++) {
const mr = row + r
const mc = col + c
if (mr < 0 || mc < 0 || mr >= matrix.length || mc >= matrix.length)
continue
if (matrix[mr][mc] !== null) continue
if (
Math.abs(r) === 2 ||
Math.abs(c) === 2 ||
(r === 0 && c === 0)
) {
matrix[mr][mc] = true
} else {
matrix[mr][mc] = false
}
}
}
}
function encodeData(data: string): number[] {
const version = getMinVersion(data.length)
// Total codewords for version + EC level L
const totalCodewords = [0, 26, 44, 70, 100][version]!
const ecCodewords = [0, 7, 10, 15, 20][version]!
const dataCodewords = totalCodewords - ecCodewords
// Byte mode: mode indicator (0100) + char count + data
const bits: number[] = []
// Mode: byte (0100)
pushBits(bits, 0b0100, 4)
// Character count (8 bits for version 1-9)
pushBits(bits, data.length, 8)
// Data
for (let i = 0; i < data.length; i++) {
pushBits(bits, data.charCodeAt(i), 8)
}
// Terminator
const maxBits = dataCodewords * 8
const termLen = Math.min(4, maxBits - bits.length)
pushBits(bits, 0, termLen)
// Pad to byte boundary
while (bits.length % 8 !== 0) bits.push(0)
// Pad codewords
const padWords = [0xec, 0x11]
let padIdx = 0
while (bits.length < maxBits) {
pushBits(bits, padWords[padIdx % 2], 8)
padIdx++
}
// Convert to bytes
const dataBytes: number[] = []
for (let i = 0; i < bits.length; i += 8) {
let byte = 0
for (let j = 0; j < 8; j++) {
byte = (byte << 1) | (bits[i + j] || 0)
}
dataBytes.push(byte)
}
// Generate EC codewords using Reed-Solomon
const ecBytes = generateEC(dataBytes, ecCodewords)
return [...dataBytes, ...ecBytes]
}
function pushBits(arr: number[], value: number, count: number) {
for (let i = count - 1; i >= 0; i--) {
arr.push((value >> i) & 1)
}
}
// GF(256) arithmetic for Reed-Solomon
const GF_EXP = new Uint8Array(512)
const GF_LOG = new Uint8Array(256)
;(function initGF() {
let x = 1
for (let i = 0; i < 255; i++) {
GF_EXP[i] = x
GF_LOG[x] = i
x = x << 1
if (x >= 256) x ^= 0x11d
}
for (let i = 255; i < 512; i++) {
GF_EXP[i] = GF_EXP[i - 255]
}
})()
function gfMul(a: number, b: number): number {
if (a === 0 || b === 0) return 0
return GF_EXP[GF_LOG[a] + GF_LOG[b]]
}
function generateEC(data: number[], ecLen: number): number[] {
// Build generator polynomial
let gen = [1]
for (let i = 0; i < ecLen; i++) {
const newGen = new Array(gen.length + 1).fill(0)
for (let j = 0; j < gen.length; j++) {
newGen[j] ^= gen[j]
newGen[j + 1] ^= gfMul(gen[j], GF_EXP[i])
}
gen = newGen
}
const result = new Array(ecLen).fill(0)
const msg = [...data, ...result]
for (let i = 0; i < data.length; i++) {
const coef = msg[i]
if (coef !== 0) {
for (let j = 0; j < gen.length; j++) {
msg[i + j] ^= gfMul(gen[j], coef)
}
}
}
return msg.slice(data.length)
}
function isReserved(
_matrix: (boolean | null)[][],
r: number,
c: number,
size: number,
version: number
): boolean {
// Finder + separator areas
if (r <= 8 && c <= 8) return true
if (r <= 8 && c >= size - 8) return true
if (r >= size - 8 && c <= 8) return true
// Timing
if (r === 6 || c === 6) return true
// Dark module
if (r === size - 8 && c === 8) return true
// Alignment patterns (version >= 2)
if (version >= 2) {
const pos = alignmentPositions[version]
if (pos) {
for (const ar of pos) {
for (const ac of pos) {
if (ar <= 8 && ac <= 8) continue
if (ar <= 8 && ac >= size - 8) continue
if (ar >= size - 8 && ac <= 8) continue
if (Math.abs(r - ar) <= 2 && Math.abs(c - ac) <= 2) return true
}
}
}
}
return false
}
function placeData(
matrix: (boolean | null)[][],
codewords: number[],
size: number
) {
let bitIdx = 0
const totalBits = codewords.length * 8
let col = size - 1
let upward = true
while (col >= 0) {
if (col === 6) col-- // skip timing column
const rows = upward
? Array.from({ length: size }, (_, i) => size - 1 - i)
: Array.from({ length: size }, (_, i) => i)
for (const row of rows) {
for (const dc of [0, -1]) {
const c = col + dc
if (c < 0 || c >= size) continue
if (matrix[row][c] !== null) continue
if (bitIdx < totalBits) {
const byteIdx = Math.floor(bitIdx / 8)
const bitPos = 7 - (bitIdx % 8)
matrix[row][c] = ((codewords[byteIdx] >> bitPos) & 1) === 1
bitIdx++
} else {
matrix[row][c] = false
}
}
}
col -= 2
upward = !upward
}
}
function placeFormatInfo(matrix: boolean[][], size: number) {
// Format info for EC level L (01) and mask 0 (000) = 01000
// After BCH: 0x77c0... Let's use the precomputed value
// EC L = 01, mask 0 = 000 -> data = 01000
// Format string after BCH and XOR with 101010000010010:
// const formatBits = 0x77c0 // L, mask 0 (unused, computed via getFormatInfo below)
// Actually, let's compute it properly
// data = 01 000 = 0b01000 = 8
// Generator: 10100110111 (0x537)
// BCH encode then XOR with mask pattern
const formatInfo = getFormatInfo(0, 0) // ecl=L(01->index 0 in our simplified), mask=0
// Place format info
const bits: boolean[] = []
for (let i = 14; i >= 0; i--) {
bits.push(((formatInfo >> i) & 1) === 1)
}
// Around top-left finder
const positions1 = [
[8, 0],
[8, 1],
[8, 2],
[8, 3],
[8, 4],
[8, 5],
[8, 7],
[8, 8],
[7, 8],
[5, 8],
[4, 8],
[3, 8],
[2, 8],
[1, 8],
[0, 8],
]
// Around other finders
const positions2 = [
[size - 1, 8],
[size - 2, 8],
[size - 3, 8],
[size - 4, 8],
[size - 5, 8],
[size - 6, 8],
[size - 7, 8],
[8, size - 8],
[8, size - 7],
[8, size - 6],
[8, size - 5],
[8, size - 4],
[8, size - 3],
[8, size - 2],
[8, size - 1],
]
for (let i = 0; i < 15; i++) {
const [r1, c1] = positions1[i]
matrix[r1][c1] = bits[i]
const [r2, c2] = positions2[i]
matrix[r2][c2] = bits[i]
}
}
function getFormatInfo(_ecl: number, mask: number): number {
// Pre-computed format info strings for EC level L with each mask
// EC Level L = 01
const formatInfoL = [
0x77c4, // mask 0
0x72f3, // mask 1
0x7daa, // mask 2
0x789d, // mask 3
0x662f, // mask 4
0x6318, // mask 5
0x6c41, // mask 6
0x6976, // mask 7
]
return formatInfoL[mask] ?? 0x77c4
}
// ── QR Canvas Component ──────────────────────────────────────────────────────
function QRCode({
data,
size = 200,
fgColor = '#e2e8f0',
bgColor = 'transparent',
}: {
data: string
size?: number
fgColor?: string
bgColor?: string
}) {
const canvasRef = useRef<HTMLCanvasElement>(null)
useEffect(() => {
const canvas = canvasRef.current
if (!canvas) return
const ctx = canvas.getContext('2d')
if (!ctx) return
try {
const matrix = generateQRMatrix(data)
const moduleCount = matrix.length
const moduleSize = size / moduleCount
canvas.width = size
canvas.height = size
// Background
ctx.fillStyle = bgColor
ctx.fillRect(0, 0, size, size)
// Modules
ctx.fillStyle = fgColor
for (let r = 0; r < moduleCount; r++) {
for (let c = 0; c < moduleCount; c++) {
if (matrix[r][c]) {
ctx.fillRect(
Math.floor(c * moduleSize),
Math.floor(r * moduleSize),
Math.ceil(moduleSize),
Math.ceil(moduleSize)
)
}
}
}
} catch {
// Fallback: draw a placeholder
canvas.width = size
canvas.height = size
ctx.fillStyle = bgColor
ctx.fillRect(0, 0, size, size)
ctx.fillStyle = fgColor
ctx.font = '14px sans-serif'
ctx.textAlign = 'center'
ctx.fillText('QR Code', size / 2, size / 2)
}
}, [data, size, fgColor, bgColor])
return <canvas ref={canvasRef} width={size} height={size} className="rounded" />
}
// ── About Page ───────────────────────────────────────────────────────────────
function AboutPage() {
const [copied, setCopied] = useState(false)
const [showQR, setShowQR] = useState(false)
const copyAddress = async () => {
try {
await navigator.clipboard.writeText(BTC_ADDRESS)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
} catch {
// Fallback for older browsers
const el = document.createElement('textarea')
el.value = BTC_ADDRESS
document.body.appendChild(el)
el.select()
document.execCommand('copy')
document.body.removeChild(el)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}
}
return (
<div className="p-6 max-w-2xl mx-auto space-y-8">
{/* Header */}
<div className="text-center space-y-3">
<RugLogo size={64} />
<h1 className="text-2xl font-bold text-text-primary">TOD - The Other Dude</h1>
<p className="text-text-secondary">
MSP fleet management platform for RouterOS devices
</p>
<span className="inline-block px-3 py-1 text-xs font-mono font-medium text-accent bg-accent-muted rounded-full">
v9.6
</span>
</div>
{/* Features summary */}
<div className="rounded-lg border border-border bg-surface p-5 space-y-3">
<h2 className="text-sm font-semibold text-text-primary uppercase tracking-wider">
Platform
</h2>
<div className="grid grid-cols-2 gap-3 text-sm text-text-secondary">
<div className="flex items-center gap-2">
<div className="w-1.5 h-1.5 rounded-full bg-accent" />
Multi-tenant with RLS
</div>
<div className="flex items-center gap-2">
<div className="w-1.5 h-1.5 rounded-full bg-accent" />
RouterOS binary API
</div>
<div className="flex items-center gap-2">
<div className="w-1.5 h-1.5 rounded-full bg-accent" />
Real-time monitoring
</div>
<div className="flex items-center gap-2">
<div className="w-1.5 h-1.5 rounded-full bg-accent" />
Safe config push
</div>
<div className="flex items-center gap-2">
<div className="w-1.5 h-1.5 rounded-full bg-accent" />
Certificate authority
</div>
<div className="flex items-center gap-2">
<div className="w-1.5 h-1.5 rounded-full bg-accent" />
WireGuard VPN
</div>
</div>
</div>
{/* Support Development */}
<div className="rounded-lg border border-border bg-surface p-5 space-y-4">
<h2 className="text-sm font-semibold text-text-primary uppercase tracking-wider">
Support Development
</h2>
<p className="text-sm text-text-muted">
The Other Dude is free and open-source. If you find it valuable,
voluntary Bitcoin contributions are appreciated but never expected.
</p>
<button
onClick={() => setShowQR(!showQR)}
className="text-xs text-accent hover:underline"
>
{showQR ? 'Hide donation address' : 'Show donation address'}
</button>
{showQR && (
<div className="flex flex-col items-center gap-4 py-4">
<div className="p-3 rounded-lg bg-background border border-border">
<QRCode
data={`bitcoin:${BTC_ADDRESS}`}
size={160}
fgColor="hsl(215 20.2% 75.1%)"
bgColor="transparent"
/>
</div>
<div className="w-full space-y-2">
<p className="text-xs text-text-muted text-center">Bitcoin Address</p>
<button
onClick={copyAddress}
className="w-full flex items-center justify-between gap-2 px-3 py-2 rounded-md bg-background border border-border text-xs font-mono text-text-secondary hover:text-text-primary hover:border-accent/50 transition-colors cursor-pointer"
title="Click to copy"
>
<span className="truncate">{BTC_ADDRESS}</span>
<span className="flex-shrink-0 text-text-muted">
{copied ? (
<svg
className="w-4 h-4 text-success"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M5 13l4 4L19 7"
/>
</svg>
) : (
<svg
className="w-4 h-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
<path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" />
</svg>
)}
</span>
</button>
</div>
</div>
)}
</div>
{/* Footer */}
<p className="text-center text-xs text-text-muted">
Not affiliated with or endorsed by MikroTik (SIA Mikrotikls)
</p>
</div>
)
}