adding freight web and holding
This commit is contained in:
2
packages/freight-web/.gitignore
vendored
Normal file
2
packages/freight-web/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
*.env
|
||||
6
packages/freight-web/.prettierignore
Normal file
6
packages/freight-web/.prettierignore
Normal file
@@ -0,0 +1,6 @@
|
||||
node_modules/
|
||||
dist/
|
||||
build/
|
||||
coverage/
|
||||
*.min.js
|
||||
*.min.css
|
||||
8
packages/freight-web/.prettierrc
Normal file
8
packages/freight-web/.prettierrc
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 100,
|
||||
"tabWidth": 4,
|
||||
"plugins": ["@ripple-ts/prettier-plugin"]
|
||||
}
|
||||
49
packages/freight-web/README.md
Normal file
49
packages/freight-web/README.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Ripple Basic Template
|
||||
|
||||
A minimal Ripple application template with TypeScript and Vite.
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Install dependencies:
|
||||
|
||||
```bash
|
||||
npm install # or pnpm or yarn
|
||||
```
|
||||
|
||||
2. Start the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
3. Build for production:
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Code Formatting
|
||||
|
||||
This template includes Prettier with the Ripple plugin for consistent code formatting.
|
||||
|
||||
### Available Commands
|
||||
|
||||
- `npm run format` - Format all files
|
||||
- `npm run format:check` - Check if files are formatted correctly
|
||||
|
||||
### Configuration
|
||||
|
||||
Prettier is configured in `.prettierrc` with the following settings:
|
||||
|
||||
- Uses tabs for indentation
|
||||
- Single quotes for strings
|
||||
- 100 character line width
|
||||
- Includes the `@ripple-ts/prettier-plugin` for `.ripple` file formatting
|
||||
|
||||
### VS Code Integration
|
||||
|
||||
For the best development experience, install the [Prettier VS Code extension](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) and the [Ripple VS Code extension](https://marketplace.visualstudio.com/items?itemName=ripple-ts.vscode-plugin).
|
||||
|
||||
## Learn More
|
||||
|
||||
- [Ripple Documentation](https://github.com/Ripple-TS/ripple)
|
||||
- [Vite Documentation](https://vitejs.dev/)
|
||||
3
packages/freight-web/eslint.config.js
Normal file
3
packages/freight-web/eslint.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import ripple from '@ripple-ts/eslint-plugin';
|
||||
|
||||
export default [...ripple.configs.recommended];
|
||||
36
packages/freight-web/index.html
Normal file
36
packages/freight-web/index.html
Normal file
@@ -0,0 +1,36 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="shortcut icon" type="image/ico" href="/src/assets/favicon.ico" />
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/beercss@3.12.13/dist/cdn/beer.min.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<script
|
||||
type="module"
|
||||
src="https://cdn.jsdelivr.net/npm/beercss@3.12.13/dist/cdn/beer.min.js"
|
||||
></script>
|
||||
<!-- <script
|
||||
type="module"
|
||||
src="https://cdn.jsdelivr.net/npm/material-dynamic-colors@1.1.2/dist/cdn/material-dynamic-colors.min.js"
|
||||
></script> -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="https://cdn.jsdelivr.net/npm/@phosphor-icons/web@2.1.1/src/regular/style.css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="https://cdn.jsdelivr.net/npm/@phosphor-icons/web@2.1.1/src/fill/style.css"
|
||||
/>
|
||||
<link rel="stylesheet" type="text/css" href="/src/assets/global.css" />
|
||||
<title>Ripple App</title>
|
||||
</head>
|
||||
<body id="root">
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<script src="/src/index.ts" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
35
packages/freight-web/package.json
Normal file
35
packages/freight-web/package.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "freight-web",
|
||||
"version": "1.0.0",
|
||||
"description": "A Ripple application created with create-ripple",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"lint": "eslint .",
|
||||
"serve": "vite preview",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check ."
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"eslint": "^9.0.0",
|
||||
"@ripple-ts/eslint-plugin": "latest",
|
||||
"prettier": "^3.6.2",
|
||||
"@ripple-ts/prettier-plugin": "latest",
|
||||
"typescript": "^5.9.2",
|
||||
"vite": "^7.1.4",
|
||||
"@ripple-ts/vite-plugin": "latest",
|
||||
"@ripple-ts/typescript-plugin": "latest"
|
||||
},
|
||||
"dependencies": {
|
||||
"lucide-ripple": "^0.0.6",
|
||||
"ripple": "latest",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"@star-kitten/eve": "workspace:^0.0.0"
|
||||
}
|
||||
}
|
||||
29
packages/freight-web/src/App.ripple
Normal file
29
packages/freight-web/src/App.ripple
Normal file
@@ -0,0 +1,29 @@
|
||||
import { track } from 'ripple';
|
||||
import { Quoute } from './components/calculator/Quoute.ripple';
|
||||
import { Header } from './components/Header.ripple';
|
||||
import { Footer } from './components/Footer.ripple';
|
||||
|
||||
export component App() {
|
||||
<Header />
|
||||
<main class="responsive">
|
||||
<div class="grid">
|
||||
<div class="s8">
|
||||
<Quoute />
|
||||
</div>
|
||||
<div class="s4">
|
||||
<article class="border medium no-padding center-align middle-align">
|
||||
<div class="padding">
|
||||
<h5>{'Placeholder Contract Info'}</h5>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
|
||||
<style>
|
||||
main.responsive {
|
||||
max-inline-size: min(150vw, 100rem);
|
||||
}
|
||||
</style>
|
||||
}
|
||||
BIN
packages/freight-web/src/assets/favicon.ico
Normal file
BIN
packages/freight-web/src/assets/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
39
packages/freight-web/src/assets/global.css
Normal file
39
packages/freight-web/src/assets/global.css
Normal file
@@ -0,0 +1,39 @@
|
||||
:root,
|
||||
body.dark {
|
||||
--primary: #78dc77;
|
||||
--on-primary: #00390a;
|
||||
--primary-container: #005313;
|
||||
--on-primary-container: #94f990;
|
||||
--secondary: #baccb3;
|
||||
--on-secondary: #253423;
|
||||
--secondary-container: #3b4b38;
|
||||
--on-secondary-container: #d5e8cf;
|
||||
--tertiary: #a0cfd4;
|
||||
--on-tertiary: #00363b;
|
||||
--tertiary-container: #1f4d52;
|
||||
--on-tertiary-container: #bcebf0;
|
||||
--error: #ffb4ab;
|
||||
--on-error: #690005;
|
||||
--error-container: #93000a;
|
||||
--on-error-container: #ffb4ab;
|
||||
--background: #1a1c19;
|
||||
--on-background: #e2e3dd;
|
||||
--surface: #121411;
|
||||
--on-surface: #e2e3dd;
|
||||
--surface-variant: #424940;
|
||||
--on-surface-variant: #c2c9bd;
|
||||
--outline: #8c9388;
|
||||
--outline-variant: #424940;
|
||||
--shadow: #000000;
|
||||
--scrim: #000000;
|
||||
--inverse-surface: #e2e3dd;
|
||||
--inverse-on-surface: #2f312d;
|
||||
--inverse-primary: #006e1c;
|
||||
--surface-dim: #121411;
|
||||
--surface-bright: #383a36;
|
||||
--surface-container-lowest: #0c0f0c;
|
||||
--surface-container-low: #1a1c19;
|
||||
--surface-container: #1e201d;
|
||||
--surface-container-high: #282b27;
|
||||
--surface-container-highest: #333531;
|
||||
}
|
||||
BIN
packages/freight-web/src/assets/ripple-logo-horizontal.png
Normal file
BIN
packages/freight-web/src/assets/ripple-logo-horizontal.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
5
packages/freight-web/src/components/Footer.ripple
Normal file
5
packages/freight-web/src/components/Footer.ripple
Normal file
@@ -0,0 +1,5 @@
|
||||
export component Footer() {
|
||||
<footer>
|
||||
<p>{'© 2025 Asgard Logistics'}</p>
|
||||
</footer>
|
||||
}
|
||||
12
packages/freight-web/src/components/Header.ripple
Normal file
12
packages/freight-web/src/components/Header.ripple
Normal file
@@ -0,0 +1,12 @@
|
||||
export component Header() {
|
||||
<header class="secondary-container">
|
||||
<nav>
|
||||
<button class="circle transparent">
|
||||
<i>{'rocket_launch'}</i>
|
||||
</button>
|
||||
<h3 class="max left-align">{'Asgard Logistics'}</h3>
|
||||
<a href="#" class="active">{'Freight Calculator'}</a>
|
||||
<a href="#">{'View Rates'}</a>
|
||||
</nav>
|
||||
</header>
|
||||
}
|
||||
126
packages/freight-web/src/components/calculator/Quoute.ripple
Normal file
126
packages/freight-web/src/components/calculator/Quoute.ripple
Normal file
@@ -0,0 +1,126 @@
|
||||
import { track, effect } from 'ripple';
|
||||
import { Icon } from '../core/Icon.ripple';
|
||||
import { RouteSelect } from './RouteSelect.ripple';
|
||||
import { formatNumber } from '../../lib/utils';
|
||||
import { fetchRoutes, type Route } from '../../lib/route';
|
||||
import { RouteDetails } from './RouteDetals.ripple';
|
||||
import { janice } from '@star-kitten/eve/third-party';
|
||||
|
||||
export component Quoute() {
|
||||
const routes = #[] as TrackedArray<Route>;
|
||||
let rush = track(false);
|
||||
let cargo = track('');
|
||||
|
||||
effect(async () => {
|
||||
const fetchedRoutes = await fetchRoutes();
|
||||
routes.push(...fetchedRoutes);
|
||||
});
|
||||
|
||||
const selectedRouteId = track(-1);
|
||||
const selectedRoute = track(() => {
|
||||
return routes.find((r) => r.id === @selectedRouteId);
|
||||
});
|
||||
|
||||
<article>
|
||||
<header>
|
||||
<h4>
|
||||
<i>{'calculate'}</i>
|
||||
{' Freight Calculator'}
|
||||
</h4>
|
||||
</header>
|
||||
<section>
|
||||
<fieldset>
|
||||
<legend>{'1. Select a route'}</legend>
|
||||
<RouteSelect {@routes} onChange={(id) => (@selectedRouteId = id)} />
|
||||
if (@selectedRoute) {
|
||||
<RouteDetails route={@selectedRoute} />
|
||||
}
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>{'2. Add cargo'}</legend>
|
||||
<div class="field border label textarea extra">
|
||||
<textarea onChange={(e) => (@cargo = (e.target as HTMLTextArea).value)} />
|
||||
<label>
|
||||
{`Paste your cargo here... (one item per line, e.g. Tritanium 10000)`}
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>{'3. Apply adjustments (optional)'}</legend>
|
||||
<div class="field middle-align">
|
||||
<nav>
|
||||
<div class="max">
|
||||
<h6>{'Add Rush Shipping'}</h6>
|
||||
<span class="helper">
|
||||
{'Guaranteed delivery within 24 hours or the entire contract is free!'}
|
||||
</span>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input type="checkbox" onChange={(e) => (@rush = !@rush)} />
|
||||
<span />
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="field suffix">
|
||||
<input type="number" min="1" placeholder="Additional Volume (m³)" />
|
||||
<Icon name="cube" />
|
||||
<span class="helper">
|
||||
{'In addition to the calculated volume from your cargo.'}
|
||||
</span>
|
||||
</div>
|
||||
<div class="field suffix">
|
||||
<input type="number" min="0" placeholder="Additional Collateral (ISK)" />
|
||||
<i>{'currency_exchange'}</i>
|
||||
<span class="helper">
|
||||
{'In addition to the calculated value of your cargo.'}
|
||||
</span>
|
||||
</div>
|
||||
</fieldset>
|
||||
</section>
|
||||
|
||||
<nav>
|
||||
<button class="circle small">
|
||||
<i>{'done'}</i>
|
||||
</button>
|
||||
<div>{'Select a route'}</div>
|
||||
<hr class="max" />
|
||||
<button class="circle small">{'2'}</button>
|
||||
<div>{'Add cargo'}</div>
|
||||
<hr class="max" />
|
||||
<button class="circle small" disabled>{'3'}</button>
|
||||
<div>{'Apply adjustments'}</div>
|
||||
<hr class="max" />
|
||||
<button class="circle small" disabled>{'4'}</button>
|
||||
<div>{'Calculate'}</div>
|
||||
</nav>
|
||||
<br />
|
||||
|
||||
// Calculate quote logic goes here
|
||||
<button
|
||||
class="responsive large-elevate extra"
|
||||
onClick={async () => {
|
||||
console.log('Calculating quote...');
|
||||
console.log('Selected Route ID:', @selectedRouteId);
|
||||
console.log('Rush Shipping:', @rush);
|
||||
console.log('Cargo:', @cargo);
|
||||
const appraisal = await janice.appraiseItems(
|
||||
@cargo,
|
||||
2,
|
||||
'DUyi5Q3Dod48IoswUBkEfNRs8Qf3cwNN',
|
||||
);
|
||||
console.log('Appraised Cargo:', appraisal);
|
||||
}}
|
||||
>
|
||||
{'Calculate Quote'}
|
||||
</button>
|
||||
</article>
|
||||
|
||||
<style>
|
||||
pre {
|
||||
color: var(--secondary);
|
||||
}
|
||||
</style>
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import type { Route } from '../../lib/route';
|
||||
import { formatNumber } from '../../lib/utils';
|
||||
|
||||
export component RouteDetails({ route }: { route: Route }) {
|
||||
<div class="s12">
|
||||
<fieldset>
|
||||
<legend>
|
||||
{'Route Details'}
|
||||
</legend>
|
||||
<div class="grid">
|
||||
<div class="s4">
|
||||
<fieldset>
|
||||
<legend>{'Max Volume'}</legend>
|
||||
<h6>{formatNumber(route.max_volume)}</h6>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="s4">
|
||||
<fieldset>
|
||||
<legend>{'Min Volume'}</legend>
|
||||
<h6>{formatNumber(route.min_volume || 0)}</h6>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="s4">
|
||||
<fieldset>
|
||||
<legend>{'Restrictions'}</legend>
|
||||
{'No Assembled Ships or Containers'}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="s6">
|
||||
<fieldset>
|
||||
<legend>{'Base Price'}</legend>
|
||||
<h6>{(route.isk_m3 ? formatNumber(route.isk_m3 || 0) + ' ISK/m³ + ' : route.isk_flatrate + ' ISK flatrate + ') + formatNumber(route.collat_pct) + '% of total collateral'}</h6>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="s6">
|
||||
<fieldset>
|
||||
<legend>
|
||||
{'Rush Fee '}
|
||||
<i>{'warning'}</i>
|
||||
<div class="tooltip top">{'Guaranteed delivery in 24 hours or your money back'}</div>
|
||||
</legend>
|
||||
<h6>{'+' + (route.rush_pct ? formatNumber(route.rush_pct) + '%' : route.rush_fee + ' ISK')}</h6>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { track, type TrackedArray } from 'ripple';
|
||||
import { Selector } from './Selector.ripple';
|
||||
import type { Route } from '../../lib/route';
|
||||
import { formatNumber } from '../../lib/utils';
|
||||
|
||||
export interface RouteSelectProps {
|
||||
routes: TrackedArray<Route>;
|
||||
onChange?: (id: string) => void;
|
||||
}
|
||||
|
||||
export component RouteSelect({ routes, onChange }: RouteSelectProps) {
|
||||
let typeTab = track('JF');
|
||||
|
||||
<div>
|
||||
<nav class="tabbed small">
|
||||
<a class={'JF' === @typeTab ? 'active' : ''} onClick={() => {
|
||||
@typeTab = 'JF';
|
||||
onChange?.('');
|
||||
}}>
|
||||
<i>{'rocket'}</i>
|
||||
<span>{'Jump Freighter'}</span>
|
||||
</a>
|
||||
<a class={'DST' === @typeTab ? 'active' : ''} onClick={() => {
|
||||
@typeTab = 'DST';
|
||||
onChange?.('');
|
||||
}}>
|
||||
<i>{'pallet'}</i>
|
||||
<span>{'Deep Space Transport'}</span>
|
||||
</a>
|
||||
<a class={'BR' === @typeTab ? 'active' : ''} onClick={() => {
|
||||
@typeTab = 'BR';
|
||||
onChange?.('');
|
||||
}}>
|
||||
<i>{'visibility_off'}</i>
|
||||
<span>{'Blockade Runner'}</span>
|
||||
</a>
|
||||
</nav>
|
||||
<div class={'page padding' + ('JF' === @typeTab ? ' active' : '')}>
|
||||
<Selector
|
||||
routes={@routes.filter((r) => r.route_type === 'JF')}
|
||||
onChange={(routeId: string) => {
|
||||
onChange?.(routeId);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class={'page padding' + ('DST' === @typeTab ? ' active' : '')}>
|
||||
<Selector
|
||||
routes={@routes.filter((r) => r.route_type === 'DST')}
|
||||
onChange={(routeId: string) => {
|
||||
onChange?.(routeId);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class={'page padding' + ('BR' === @typeTab ? ' active' : '')}>
|
||||
<Selector
|
||||
routes={@routes.filter((r) => r.route_type === 'BR')}
|
||||
onChange={(routeId: string) => {
|
||||
onChange?.(routeId);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { track, type TrackedArray } from 'ripple';
|
||||
import type { Route } from '../../lib/route';
|
||||
import { formatNumber } from '../../lib/utils';
|
||||
|
||||
export interface OriginDestProps {
|
||||
routes: TrackedArray<Route>;
|
||||
onChange?: (routeId: string) => void;
|
||||
}
|
||||
|
||||
export component Selector({ routes, onChange }: OriginDestProps) {
|
||||
const selectedRoute = track<Route>(undefined);
|
||||
let origin = track('');
|
||||
|
||||
const origins = track(() => {
|
||||
const uniqueOrigins = new Set<string>();
|
||||
for (const route of routes) {
|
||||
uniqueOrigins.add(route.origin);
|
||||
}
|
||||
return Array.from(uniqueOrigins);
|
||||
});
|
||||
|
||||
<div class="grid">
|
||||
<div class="s6">
|
||||
<fieldset>
|
||||
<legend>{'Origin'}</legend>
|
||||
<div class="field suffix border">
|
||||
<select
|
||||
onChange={(e) => {
|
||||
@origin = (e.target as HTMLSelectElement).value;
|
||||
@selectedRoute = undefined;
|
||||
onChange?.('');
|
||||
}}
|
||||
>
|
||||
<option value="">{'Select origin'}</option>
|
||||
for (const text of @origins) {
|
||||
<option value={text}>{text}</option>
|
||||
}
|
||||
</select>
|
||||
<i>{'arrow_drop_down'}</i>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="s6">
|
||||
<fieldset>
|
||||
<legend>{'Destination'}</legend>
|
||||
<div class="field suffix border">
|
||||
<select
|
||||
onChange={(e) => {
|
||||
const id = (e.target as HTMLSelectElement).value;
|
||||
@selectedRoute = @routes.find((r: Route) => r.id === id);
|
||||
onChange?.(id);
|
||||
}}
|
||||
>
|
||||
<option value="">{'Select a route'}</option>
|
||||
for (const route of @routes) {
|
||||
const rt = route as Route;
|
||||
if (rt.origin === @origin) {
|
||||
<option value={rt.id}>{rt.destination}</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
<i>{'arrow_drop_down'}</i>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.field {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
</style>
|
||||
}
|
||||
|
||||
8
packages/freight-web/src/components/core/Icon.ripple
Normal file
8
packages/freight-web/src/components/core/Icon.ripple
Normal file
@@ -0,0 +1,8 @@
|
||||
export interface IconProps {
|
||||
name: string;
|
||||
style?: Record<string, string | number>;
|
||||
}
|
||||
|
||||
export component Icon({ name, style }: IconProps) {
|
||||
<i class={`ph ph-${name}`} {style} />
|
||||
}
|
||||
7
packages/freight-web/src/index.ts
Normal file
7
packages/freight-web/src/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { mount } from 'ripple';
|
||||
// @ts-expect-error: known issue, we're working on it
|
||||
import { App } from './App.ripple';
|
||||
|
||||
mount(App, {
|
||||
target: document.getElementById('root'),
|
||||
});
|
||||
38
packages/freight-web/src/lib/route.ts
Normal file
38
packages/freight-web/src/lib/route.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
export interface Route {
|
||||
id: string;
|
||||
route_type: 'JF' | 'DST' | 'BR';
|
||||
origin: string;
|
||||
origin_type: string;
|
||||
destination: string;
|
||||
detination_type: string;
|
||||
max_volume: number;
|
||||
max_collat: string;
|
||||
collat_pct: number;
|
||||
isk_m3?: number;
|
||||
isk_flatrate?: string;
|
||||
min_volume?: number;
|
||||
rush_pct?: number;
|
||||
rush_fee?: string;
|
||||
}
|
||||
|
||||
export async function fetchRoutes(): Promise<Route[]> {
|
||||
const response = await fetch(
|
||||
'https://docs.google.com/spreadsheets/d/e/2PACX-1vSuUV7yKNkgSFFLz2LSUqD4WRQ71usoRuYUMKL00HsJHz52VfgFAIkE8w2DG59V93kCkEKFlqFux-OI/pub?gid=0&single=true&output=csv'
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch routes: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
const text = await response.text();
|
||||
const lines = text.trim().split('\n');
|
||||
const routes: Route[] = [];
|
||||
const headers = lines[0].split(',').map((h) => h.trim());
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
const values = lines[i].split(',').map((v) => v.trim());
|
||||
const row: any = {};
|
||||
for (let j = 0; j < headers.length; j++) {
|
||||
row[headers[j]] = values[j];
|
||||
}
|
||||
routes.push(row);
|
||||
}
|
||||
return routes;
|
||||
}
|
||||
3
packages/freight-web/src/lib/utils.ts
Normal file
3
packages/freight-web/src/lib/utils.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function formatNumber(num: number): string {
|
||||
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
}
|
||||
18
packages/freight-web/tsconfig.json
Normal file
18
packages/freight-web/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "bundler",
|
||||
"jsx": "preserve",
|
||||
"jsxImportSource": "ripple",
|
||||
"noEmit": true,
|
||||
"isolatedModules": true,
|
||||
"plugins": [{ "name": "@ripple-ts/typescript-plugin" }],
|
||||
"paths": {
|
||||
"@*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
13
packages/freight-web/vite.config.js
Normal file
13
packages/freight-web/vite.config.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import tsconfigPaths from 'vite-tsconfig-paths';
|
||||
import { ripple } from '@ripple-ts/vite-plugin';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [tsconfigPaths(), ripple()],
|
||||
server: {
|
||||
port: 3000,
|
||||
},
|
||||
build: {
|
||||
target: 'esnext',
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user