diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 1199dac..7401541 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -39,7 +39,7 @@ jobs: env: VITE_BASE_URL: '/${{ github.event.repository.name }}' VITE_YAHOO_API_URL: 'https://query1.finance.yahoo.com' - VITE_YAHOO_ORIGIN: 'https://finance.yahoo.com' + VITE_YAHOO_ORIGIN: '/${{ github.event.repository.name }}' - name: Create 404.html run: cp dist/index.html dist/404.html diff --git a/README.md b/README.md index 7e2ecf0..57026b5 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,8 @@ Why this Project? - Portfolio Performance & Value - All assets (except the TTWOR and Portfolio-Value) are scaled by percentage of their price. Thus their referenced, scale is on the right. The referenced scale on the left is only for the portfolio-value -![Dark Mode Preview](./docs/preview-dark.png) -![Light Mode Preview](./docs/preview-light.png) +![Dark Mode Preview](./docs/dark-mode.png) +![Light Mode Preview](./docs/light-mode.png) ## Features diff --git a/docs/dark-mode.png b/docs/dark-mode.png new file mode 100644 index 0000000..4775e39 Binary files /dev/null and b/docs/dark-mode.png differ diff --git a/docs/light-mode.png b/docs/light-mode.png new file mode 100644 index 0000000..1c39d33 Binary files /dev/null and b/docs/light-mode.png differ diff --git a/src/App.tsx b/src/App.tsx index 13d14e2..6a2694d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -39,11 +39,11 @@ export default function App() { -
+
-
+
diff --git a/src/components/AddAssetModal.tsx b/src/components/AddAssetModal.tsx index 20f58e6..eb46ebe 100644 --- a/src/components/AddAssetModal.tsx +++ b/src/components/AddAssetModal.tsx @@ -86,6 +86,7 @@ export const AddAssetModal = ({ onClose }: { onClose: () => void }) => { placeholder="Search by symbol or name..." className="w-full p-2 pr-10 border rounded dark:bg-slate-800 dark:border-slate-700 dark:outline-none dark:text-gray-300" value={search} + autoFocus onChange={(e) => { setSearch(e.target.value); debouncedSearch(e.target.value); diff --git a/src/components/InvestmentForm.tsx b/src/components/InvestmentForm.tsx index ce4597d..1dc4aed 100644 --- a/src/components/InvestmentForm.tsx +++ b/src/components/InvestmentForm.tsx @@ -19,7 +19,7 @@ export const InvestmentFormWrapper = () => { return (
-
+

Add Investment

{assets.length > 0 && ( @@ -50,8 +50,10 @@ export const InvestmentFormWrapper = () => {
{ selectedAsset && ( -
- +
+
+ setSelectedAsset(null)} /> +
) } @@ -59,7 +61,7 @@ export const InvestmentFormWrapper = () => { ); } -const InvestmentForm = ({ assetId }: { assetId: string }) => { +const InvestmentForm = ({ assetId, clearSelectedAsset }: { assetId: string, clearSelectedAsset: () => void }) => { const [type, setType] = useState<'single' | 'periodic'>('single'); const [amount, setAmount] = useState(''); const [date, setDate] = useState(''); @@ -114,6 +116,7 @@ const InvestmentForm = ({ assetId }: { assetId: string }) => { } // Reset form setAmount(''); + clearSelectedAsset(); }; return ( @@ -134,6 +137,7 @@ const InvestmentForm = ({ assetId }: { assetId: string }) => { setAmount(e.target.value)} className="w-full p-2 border rounded dark:bg-slate-800 dark:border-slate-700 dark:outline-none dark:text-gray-300" @@ -168,7 +172,7 @@ const InvestmentForm = ({ assetId }: { assetId: string }) => { required />
- + { tickFormatter={(value) => `${value.toFixed(2)}%`} /> { const assetKey = name.split('_')[0] as keyof typeof assets; const processedKey = `${assets.find(a => a.name === name.replace(" (%)", ""))?.id}_price`; diff --git a/src/components/PortfolioTable.tsx b/src/components/PortfolioTable.tsx index 51ee5d4..5f7cfff 100644 --- a/src/components/PortfolioTable.tsx +++ b/src/components/PortfolioTable.tsx @@ -177,12 +177,12 @@ export const PortfolioTable = () => { {investment?.type === 'periodic' ? ( - Sparplan + SavingsPlan ) : ( - Einmalig + OneTime )} diff --git a/src/index.css b/src/index.css index 44b7d0c..3525aeb 100644 --- a/src/index.css +++ b/src/index.css @@ -74,3 +74,51 @@ body { html, body { background: inherit; } + +/* Scrollbar Styling für Investment Form */ +.scrollbar-styled { + scrollbar-gutter: stable both-edges; + overflow-y: scroll !important; +} + +.scrollbar-styled::-webkit-scrollbar { + width: 8px; + height: 8px; + display: block; +} + +.scrollbar-styled::-webkit-scrollbar-track { + background: transparent; + margin: 4px; +} + +.scrollbar-styled::-webkit-scrollbar-thumb { + background-color: #94a3b8; + border-radius: 9999px; + border: 2px solid transparent; + background-clip: content-box; + min-height: 40px; +} + +.scrollbar-styled::-webkit-scrollbar-thumb:hover { + background-color: #64748b; +} + +/* Dark mode */ +.dark .scrollbar-styled::-webkit-scrollbar-thumb { + background-color: #475569; +} + +.dark .scrollbar-styled::-webkit-scrollbar-thumb:hover { + background-color: #64748b; +} + +/* Firefox */ +.scrollbar-styled { + scrollbar-width: thin; + scrollbar-color: #94a3b8 transparent; +} + +.dark .scrollbar-styled { + scrollbar-color: #475569 transparent; +} diff --git a/src/services/yahooFinanceService.ts b/src/services/yahooFinanceService.tsx similarity index 84% rename from src/services/yahooFinanceService.ts rename to src/services/yahooFinanceService.tsx index 061a924..915f974 100644 --- a/src/services/yahooFinanceService.ts +++ b/src/services/yahooFinanceService.tsx @@ -37,7 +37,8 @@ interface YahooChartResult { }; } -const API_BASE = import.meta.env.VITE_YAHOO_API_URL || '/yahoo'; +const isDev = import.meta.env.DEV; +const API_BASE = isDev ? '/yahoo' : 'https://api.allorigins.win/raw?url=' + encodeURIComponent('https://query1.finance.yahoo.com'); export const searchAssets = async (query: string): Promise => { try { @@ -47,11 +48,11 @@ export const searchAssets = async (query: string): Promise => { type: 'equity,etf', }); - const response = await fetch(`${API_BASE}/v1/finance/lookup?${params}`, { - headers: { - 'Origin': import.meta.env.VITE_YAHOO_ORIGIN || window.location.origin - } - }); + const url = isDev + ? `${API_BASE}/v1/finance/lookup?${params}` + : `${API_BASE}/v1/finance/lookup?${params}`; + + const response = await fetch(url); if (!response.ok) throw new Error('Network response was not ok'); const data = await response.json() as YahooSearchResponse; @@ -96,7 +97,11 @@ export const getHistoricalData = async (symbol: string, startDate: string, endDa interval: '1d', }); - const response = await fetch(`/yahoo/v8/finance/chart/${symbol}?${params}`); + const url = isDev + ? `/yahoo/v8/finance/chart/${symbol}?${params}` + : `${API_BASE}/v8/finance/chart/${symbol}?${params}`; + + const response = await fetch(url); if (!response.ok) throw new Error('Network response was not ok'); const data = await response.json(); diff --git a/vite.config.ts b/vite.config.ts index be1f3c8..54d6982 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,6 +5,7 @@ import react from "@vitejs/plugin-react"; // https://vitejs.dev/config/ export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd(), ''); + const isDev = mode === 'development'; return { plugins: [react()], @@ -12,16 +13,16 @@ export default defineConfig(({ mode }) => { exclude: ['lucide-react'], }, server: { - proxy: { + proxy: isDev ? { '/yahoo': { - target: env.VITE_YAHOO_API_URL || 'https://query1.finance.yahoo.com', + target: 'https://query1.finance.yahoo.com', changeOrigin: true, rewrite: (path) => path.replace(/^\/yahoo/, ''), headers: { - 'Origin': env.VITE_YAHOO_ORIGIN || 'https://finance.yahoo.com' + 'Origin': 'https://finance.yahoo.com' } } - } + } : undefined }, base: env.VITE_BASE_URL || '/', };