diff --git a/.env b/.env new file mode 100644 index 0000000..3842f49 --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +VITE_DOMAIN_DEV='http://localhost:5000' +VITE_DOMAIN_PROD='https://sean.bet/trigger' +VITE_BASE_URL='trigger' \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d669e7c..4ac8c08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "@tanstack/react-query-persist-client": "^5.51.23", "clsx": "^2.1.1", "dayjs": "^1.11.12", + "dotenv": "^16.4.7", "idb-keyval": "^6.2.1", "jotai": "^2.9.3", "mantine-react-table": "^2.0.0-beta.6", @@ -32,6 +33,7 @@ "react-toastify": "^10.0.5" }, "devDependencies": { + "@types/node": "^22.10.1", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@typescript-eslint/eslint-plugin": "^8.1.0", @@ -1706,6 +1708,15 @@ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, + "node_modules/@types/node": { + "version": "22.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", + "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", + "dev": true, + "dependencies": { + "undici-types": "~6.20.0" + } + }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", @@ -2261,6 +2272,17 @@ "node": ">=8" } }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.768", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.768.tgz", @@ -4064,6 +4086,12 @@ "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true + }, "node_modules/unload": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz", diff --git a/package.json b/package.json index 4632785..bf69974 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@tanstack/react-query-persist-client": "^5.51.23", "clsx": "^2.1.1", "dayjs": "^1.11.12", + "dotenv": "^16.4.7", "idb-keyval": "^6.2.1", "jotai": "^2.9.3", "mantine-react-table": "^2.0.0-beta.6", @@ -34,6 +35,7 @@ "react-toastify": "^10.0.5" }, "devDependencies": { + "@types/node": "^22.10.1", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@typescript-eslint/eslint-plugin": "^8.1.0", diff --git a/src/assets/backgrounds/panam.png b/public/backgrounds/panam.png similarity index 100% rename from src/assets/backgrounds/panam.png rename to public/backgrounds/panam.png diff --git a/src/assets/backgrounds/peek.jpg b/public/backgrounds/peek.jpg similarity index 100% rename from src/assets/backgrounds/peek.jpg rename to public/backgrounds/peek.jpg diff --git a/src/assets/backgrounds/quadra.jpg b/public/backgrounds/quadra.jpg similarity index 100% rename from src/assets/backgrounds/quadra.jpg rename to public/backgrounds/quadra.jpg diff --git a/src/assets/backgrounds/redporsche.jpg b/public/backgrounds/redporsche.jpg similarity index 100% rename from src/assets/backgrounds/redporsche.jpg rename to public/backgrounds/redporsche.jpg diff --git a/src/assets/backgrounds/touch.jpg b/public/backgrounds/touch.jpg similarity index 100% rename from src/assets/backgrounds/touch.jpg rename to public/backgrounds/touch.jpg diff --git a/src/hooks/useAPI.ts b/src/hooks/useAPI.ts index b7e36f4..a4c5ef4 100644 --- a/src/hooks/useAPI.ts +++ b/src/hooks/useAPI.ts @@ -7,9 +7,10 @@ import { import { useCallback } from 'react'; import { toast } from 'react-toastify'; +const url = `${import.meta.env.prod ? import.meta.env.VITE_DOMAIN_PROD : import.meta.env.VITE_DOMAIN_DEV}/${import.meta.env.VITE_BASE_URL}`; + export function useQueryAPI({ endpoint }: { endpoint: string }) { const callEndpoint = async (endpoint: string) => { - const url = `http://localhost:5000/trigger/${endpoint}`; const options: object = { method: 'GET', headers: { @@ -23,7 +24,7 @@ export function useQueryAPI({ endpoint }: { endpoint: string }) { }, }; - const response = await fetch(url, options); + const response = await fetch(`${url}/${endpoint}`, options); if (!response.ok) { throw new Error(`${response.status} ${response.statusText}`); } @@ -38,13 +39,13 @@ export function useQueryAPI({ endpoint }: { endpoint: string }) { export function useWeatherAPI({ endpoint }: { endpoint: string }) { let url = ''; - if (endpoint === 'baseline') { - url = 'https://api.weather.gov/gridpoints/HGX/55,96'; - } else if (endpoint === 'current') { - url = 'https://api.weather.gov/stations/KSGR/observations'; - } else if (endpoint === 'forecast') { - url = 'https://api.weather.gov/gridpoints/HGX/55,96/forecast'; - } + if (endpoint === 'baseline') { + url = 'https://api.weather.gov/gridpoints/HGX/55,96'; + } else if (endpoint === 'current') { + url = 'https://api.weather.gov/stations/KSGR/observations'; + } else if (endpoint === 'forecast') { + url = 'https://api.weather.gov/gridpoints/HGX/55,96/forecast'; + } const callEndpoint = async (endpoint: string) => { const options: object = { method: 'GET', @@ -77,7 +78,6 @@ export function useMutationAPI() { endpoint: string, body: object | undefined | null, ) => { - const url = `http://localhost:5000/trigger/${endpoint}`; const options: any = { method: method, headers: { @@ -158,7 +158,6 @@ export function useFileUploadAPI() { endpoint: string, body?: object | FormData | undefined | null, ) => { - const url = `http://localhost:5000/trigger/${endpoint}`; const options = { method: method, @@ -231,19 +230,19 @@ export function useFetchAllRecordsAPI({ endpoint }: { endpoint: string }) { const fetchPage = async (page: number, retryCount = 0): Promise => { // Start building the URL - const url = new URL(`http://localhost:5000/trigger/${endpoint}`); + const urlObject = new URL(`${url}/${endpoint}`); // Check and append custom query parameters from the endpoint const customParams = new URLSearchParams(endpoint.split('?')[1]); for (const [key, value] of customParams) { - if (!url.searchParams.has(key)) { - url.searchParams.append(key, value); + if (!urlObject.searchParams.has(key)) { + urlObject.searchParams.append(key, value); } } // Add 'page' and 'size' parameters - url.searchParams.set('page', page.toString()); // Use 'set' to ensure uniqueness - url.searchParams.set('size', pageSize.toString()); + urlObject.searchParams.set('page', page.toString()); // Use 'set' to ensure uniqueness + urlObject.searchParams.set('size', pageSize.toString()); const options: any = { method: 'GET', @@ -345,7 +344,6 @@ interface DownloadFileOptions { export function useDownloadFile() { const downloadFile = useCallback( async ({ endpoint, file_name }: DownloadFileOptions) => { - const url = `http://localhost:5000/trigger/${endpoint}`; const options: any = { method: 'GET', headers: { @@ -426,7 +424,6 @@ export const useFetchInfiniteQuery = ({ const queryKey = ['GET', endpoint]; const fetchInfiniteQuery = async (endpoint: string) => { - const url = `http://localhost:5000/trigger/${endpoint}`; const requestOptions: any = { method: 'GET', headers: { diff --git a/src/pages/homepage/components/Header.tsx b/src/pages/homepage/components/Header.tsx index 9e3942a..78ef399 100644 --- a/src/pages/homepage/components/Header.tsx +++ b/src/pages/homepage/components/Header.tsx @@ -1,7 +1,7 @@ import { Group, Stack, Text } from '@mantine/core'; import { useEffect, useState } from 'react'; -import { textClasses } from '../../../styles'; import { useWeatherAPI } from '../../../hooks'; +import { textClasses } from '../../../styles'; function Header({ greet, date, isMobile }: any) { const [time, setTime] = useState(new Date()); @@ -105,32 +105,28 @@ function Header({ greet, date, isMobile }: any) { width: '100%', gap: 0, }} - > + > {!isMobile && ( - - {current.temperature}°F - - - {current.highTemp}°F/{current.lowTemp}°F - - + + {current.temperature}°F + + + {current.highTemp}°F/{current.lowTemp}°F + + )} - + {`${ time.getHours() === 0 ? 12 diff --git a/src/pages/homepage/components/HelpModal.tsx b/src/pages/homepage/components/HelpModal.tsx new file mode 100644 index 0000000..b341c1c --- /dev/null +++ b/src/pages/homepage/components/HelpModal.tsx @@ -0,0 +1,59 @@ +import { Divider, Group, Modal, Stack, Text } from '@mantine/core'; +import { useAtom } from 'jotai'; +import { appTheme } from '../../../contexts/theme'; +import { modalClasses } from '../../../styles'; + +export default function HelpModal({ + setValue, + setProviderLabel, + setProviderURL, + providers, + providerModalOpened, + closeProviderModal, +}: any) { + const [theme, setTheme] = useAtom(appTheme); + return ( + { + closeProviderModal(); + setValue(''); + setProviderLabel('Google Search'); + setProviderURL('https://www.google.com/search?q='); + }} + style={{ + '--color-text-pri': theme.primaryColor, + '--color-text-acc': theme.accentColor, + '--color-background': theme.backgroundColor, + '--color-border': theme.borderColor, + '--color-search-background': theme.searchColor, + '--image-background': `url(${theme.backgroundImage})`, + }} + closeOnEscape + closeOnClickOutside + > + + + + Help info + + + + +

Search Providers

+ + {providers?.map((provider: any) => ( + + {provider.prefix} + {provider.name} + + ))} +
+
+
+
+ ); +} diff --git a/src/pages/homepage/components/Search.tsx b/src/pages/homepage/components/Search.tsx index b80d2d9..d04d969 100644 --- a/src/pages/homepage/components/Search.tsx +++ b/src/pages/homepage/components/Search.tsx @@ -1,12 +1,10 @@ -import { Center, Divider, Group, Modal, Stack, Text, TextInput } from '@mantine/core'; +import { Stack, TextInput } from '@mantine/core'; import { useDisclosure } from '@mantine/hooks'; -import { useAtom } from 'jotai'; import { useEffect, useState } from 'react'; -import { appTheme } from '../../../contexts/theme'; import { useQueryAPI, useWeatherAPI } from '../../../hooks'; -import { modalClasses, textInputClasses } from '../../../styles'; -import SmallWeatherCard from './SmallWeatherCard'; -import SmallOutlookCard from './SmallOutlookCard'; +import { textInputClasses } from '../../../styles'; +import HelpModal from './HelpModal'; +import WeatherModal from './WeatherModal'; export default function Search() { const { data: providers } = useQueryAPI({ endpoint: 'api/providers' }); @@ -31,8 +29,8 @@ export default function Search() { temperature: 0, precipitation: 0, windSpeed: '0 mph', - windDirection: 'N' - } + windDirection: 'N', + }, ]); const { data: forecast } = useWeatherAPI({ endpoint: 'forecast' }); const [providerModalOpened, { open: openProviderModal, close: closeProviderModal }] = @@ -42,7 +40,6 @@ export default function Search() { const [providerLabel, setProviderLabel] = useState('Google Search'); const [providerURL, setProviderURL] = useState('https://www.google.com/search?q='); const [value, setValue] = useState(''); - const [theme, setTheme] = useAtom(appTheme); const handleKeyPress = (item: any) => { let option = ''; let subtext = ''; @@ -93,7 +90,6 @@ export default function Search() { useEffect(() => { if (weather && basicWeather) { - console.log(weather.features[0]) setCurrent({ icon: weather.features[0].properties.icon?.replace('medium', 'large') || '', temperature: Math.round( @@ -125,22 +121,26 @@ export default function Search() { icon: forecast.properties.periods[0].icon.replace('medium', 'large'), isDayTime: forecast.properties.periods[0].isDaytime, temperature: forecast.properties.periods[0].temperature, - precipitation: Math.round(forecast.properties.periods[0].probabilityOfPrecipitation.value), + precipitation: Math.round( + forecast.properties.periods[0].probabilityOfPrecipitation.value, + ), windSpeed: forecast.properties.periods[0].windSpeed, - windDirection: forecast.properties.periods[0].windDirection + windDirection: forecast.properties.periods[0].windDirection, }, { name: forecast.properties.periods[1].name, icon: forecast.properties.periods[1].icon.replace('medium', 'large'), isDayTime: forecast.properties.periods[1].isDaytime, temperature: forecast.properties.periods[1].temperature, - precipitation: Math.round(forecast.properties.periods[1].probabilityOfPrecipitation.value), + precipitation: Math.round( + forecast.properties.periods[1].probabilityOfPrecipitation.value, + ), windSpeed: forecast.properties.periods[1].windSpeed, - windDirection: forecast.properties.periods[1].windDirection - } + windDirection: forecast.properties.periods[1].windDirection, + }, ]); } - }, [forecast]) + }, [forecast]); return ( - { - closeProviderModal(); - setValue(''); - setProviderLabel('Google Search'); - setProviderURL('https://www.google.com/search?q='); - }} - style={{ - '--color-text-pri': theme.primaryColor, - '--color-text-acc': theme.accentColor, - '--color-background': theme.backgroundColor, - '--color-border': theme.borderColor, - '--color-search-background': theme.searchColor, - '--image-background': `url(${theme.backgroundImage})`, - }} - closeOnEscape - closeOnClickOutside - > - - - - Help info - - - - -

Search Providers

- - {providers?.map((provider: any) => ( - - {provider.prefix} - {provider.name} - - ))} -
-
-
-
- { - closeWeatherModal(); - setValue(''); - setProviderLabel('Google Search'); - setProviderURL('https://www.google.com/search?q='); - }} - style={{ - '--color-text-pri': theme.primaryColor, - '--color-text-acc': theme.accentColor, - '--color-background': theme.backgroundColor, - '--color-border': theme.borderColor, - '--color-search-background': theme.searchColor, - '--image-background': `url(${theme.backgroundImage})`, - }} - closeOnEscape - closeOnClickOutside - > - - - - Weather Forecast - - - -
- -

Currently

- - - -
- - - {outlook.map((o: any) => ( - -

{o.name}

- - - -
- ))} -
-
-
-
-
+ +
); } diff --git a/src/pages/homepage/components/SmallWeatherCard.tsx b/src/pages/homepage/components/SmallWeatherCard.tsx index 8186e7e..aeea453 100644 --- a/src/pages/homepage/components/SmallWeatherCard.tsx +++ b/src/pages/homepage/components/SmallWeatherCard.tsx @@ -14,9 +14,9 @@ export default function SmallWeatherCard({ weather }: any) { /> - - {weather.highTemp}°F/{weather.lowTemp}°F - + + {weather.highTemp}°F/{weather.lowTemp}°F + {weather.precipitation !== 'N/A' && ( diff --git a/src/pages/homepage/components/WeatherModal.tsx b/src/pages/homepage/components/WeatherModal.tsx new file mode 100644 index 0000000..d43d5fb --- /dev/null +++ b/src/pages/homepage/components/WeatherModal.tsx @@ -0,0 +1,72 @@ +import { Center, Divider, Group, Modal, Stack } from '@mantine/core'; +import { useAtom } from 'jotai'; +import { modalClasses } from '../../../styles'; +import SmallOutlookCard from './SmallOutlookCard'; +import SmallWeatherCard from './SmallWeatherCard'; + +import { appTheme } from '../../../contexts/theme'; + +export default function WeatherModal({ + current, + outlook, + closeWeatherModal, + weatherModalOpened, + setValue, + setProviderLabel, + setProviderURL, +}: any) { + const [theme, setTheme] = useAtom(appTheme); + return ( + { + closeWeatherModal(); + setValue(''); + setProviderLabel('Google Search'); + setProviderURL('https://www.google.com/search?q='); + }} + style={{ + '--color-text-pri': theme.primaryColor, + '--color-text-acc': theme.accentColor, + '--color-background': theme.backgroundColor, + '--color-border': theme.borderColor, + '--color-search-background': theme.searchColor, + '--image-background': `url(${theme.backgroundImage})`, + }} + closeOnEscape + closeOnClickOutside + > + + + + Weather Forecast + + + +
+ +

Currently

+ + + +
+ + + {outlook.map((o: any) => ( + +

{o.name}

+ + + +
+ ))} +
+
+
+
+
+ ); +} diff --git a/src/pages/homepage/types/weather.d.ts b/src/pages/homepage/types/weather.d.ts index 4d6a7ce..682f3c9 100644 --- a/src/pages/homepage/types/weather.d.ts +++ b/src/pages/homepage/types/weather.d.ts @@ -1,4 +1,4 @@ -interface CurrentWeather{ +interface CurrentWeather { icon: string; temperature: number; highTemp: number; @@ -10,7 +10,7 @@ interface CurrentWeather{ windDirection: number; } -interface OutlookWeather{ +interface OutlookWeather { name: string; icon: string; isDayTime: boolean; @@ -18,4 +18,4 @@ interface OutlookWeather{ precipitation: number; windSpeed: string; windDirection: string; -} \ No newline at end of file +} diff --git a/vite.config.ts b/vite.config.ts index 6654463..cb19c90 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,29 +1,32 @@ import react from '@vitejs/plugin-react'; -import { defineConfig } from 'vite'; +import { defineConfig, loadEnv } from 'vite'; // https://vitejs.dev/config/ -export default defineConfig({ - plugins: [react()], - server: { - port: 3000, - host: '0.0.0.0', - proxy: { - '/api': { - target: 'http://localhost:5000/trigger', - changeOrigin: false, - secure: false, +export default defineConfig(({mode}) => { + const env = loadEnv(mode, process.cwd()); + return { + plugins: [react()], + server: { + port: 3000, + host: '0.0.0.0', + proxy: { + '/api': { + target: `http://localhost:5000/${env.VITE_BASE_URL}`, + changeOrigin: false, + secure: false, + }, }, }, - }, - preview: { - port: 3000, - host: '0.0.0.0', - proxy: { - api: { - target: 'http://localhost:5000', - changeOrigin: false, - secure: false, + preview: { + port: 3000, + host: '0.0.0.0', + proxy: { + api: { + target: 'http://localhost:5000', + changeOrigin: false, + secure: false, + }, }, }, - }, +} });