Bug fixes and new biome lint update

This commit is contained in:
Sean Edmunds
2025-06-17 17:00:29 -05:00
parent 4a77340ba5
commit 7570c287c8
26 changed files with 459 additions and 500 deletions

View File

@@ -1,9 +1,7 @@
{ {
"organizeImports": { "assist": { "actions": { "source": { "organizeImports": "on" } } },
"enabled": true
},
"files": { "files": {
"ignore": ["./node_modules/", "./dist/", "./.nx/"], "includes": ["./src/**/*.{ts,tsx,js,jsx}"],
"ignoreUnknown": true "ignoreUnknown": true
}, },
"linter": { "linter": {
@@ -11,8 +9,7 @@
"rules": { "rules": {
"recommended": true, "recommended": true,
"a11y": { "a11y": {
"noSvgWithoutTitle": "off", "noSvgWithoutTitle": "off"
"all": true
}, },
"complexity": { "complexity": {
"noUselessTypeConstraint": "off", "noUselessTypeConstraint": "off",
@@ -20,9 +17,9 @@
}, },
"suspicious": { "suspicious": {
"noExplicitAny": "off", "noExplicitAny": "off",
"noConsoleLog": "error",
"noImplicitAnyLet": "off", "noImplicitAnyLet": "off",
"recommended": true "recommended": true,
"noConsole": { "level": "error", "options": { "allow": ["error", "log"] } }
}, },
"correctness": { "correctness": {
"useExhaustiveDependencies": "off", "useExhaustiveDependencies": "off",
@@ -32,12 +29,24 @@
"performance": { "performance": {
"noDelete": "off", "noDelete": "off",
"recommended": true "recommended": true
},
"style": {
"noParameterAssign": "error",
"useAsConstAssertion": "error",
"useDefaultParameterLast": "error",
"useEnumInitializers": "error",
"useSelfClosingElements": "error",
"useSingleVarDeclarator": "error",
"noUnusedTemplateLiteral": "error",
"useNumberNamespace": "error",
"noInferrableTypes": "error",
"noUselessElse": "error"
} }
} }
}, },
"formatter": { "formatter": {
"enabled": true, "enabled": true,
"formatWithErrors": false, "formatWithErrors": true,
"indentStyle": "tab", "indentStyle": "tab",
"indentWidth": 2, "indentWidth": 2,
"lineWidth": 90 "lineWidth": 90

View File

@@ -1,19 +1,19 @@
import { import {
DEFAULT_THEME,
MantineProvider,
colorsTuple, colorsTuple,
createTheme, createTheme,
DEFAULT_THEME,
MantineProvider,
mergeMantineTheme, mergeMantineTheme,
} from '@mantine/core'; } from '@mantine/core';
import { Notifications } from '@mantine/notifications'; import { Notifications } from '@mantine/notifications';
import { useCommonHooks, useQueryAPI } from 'hooks'; import { useCommonHooks, useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { import {
createBrowserRouter,
createRoutesFromElements,
Navigate, Navigate,
Route, Route,
RouterProvider, RouterProvider,
createBrowserRouter,
createRoutesFromElements,
} from 'react-router-dom'; } from 'react-router-dom';
import { Loading, MainLayout } from './components'; import { Loading, MainLayout } from './components';
import '@mantine/core/styles.css'; import '@mantine/core/styles.css';

View File

@@ -1,8 +1,8 @@
import { QueryClient } from '@tanstack/react-query'; import { QueryClient } from '@tanstack/react-query';
import { import {
PersistQueryClientProvider,
type PersistedClient, type PersistedClient,
type Persister, type Persister,
PersistQueryClientProvider,
} from '@tanstack/react-query-persist-client'; } from '@tanstack/react-query-persist-client';
import { del, get, set } from 'idb-keyval'; import { del, get, set } from 'idb-keyval';
import React, { type ComponentType } from 'react'; import React, { type ComponentType } from 'react';

View File

@@ -32,7 +32,7 @@ export default function CurrentWeather({ current }: { current: CW }) {
}, [sunStateData]); }, [sunStateData]);
return ( return (
<> <>
{current ? ( {current && (
<Stack <Stack
gap={0} gap={0}
justify='space-between' justify='space-between'
@@ -250,8 +250,6 @@ export default function CurrentWeather({ current }: { current: CW }) {
</Stack> </Stack>
</Group> </Group>
</Stack> </Stack>
) : (
<></>
)} )}
</> </>
); );

View File

@@ -6,7 +6,9 @@ import WeatherIcon from './WeatherIcon';
export default function ExtendedForecastCard({ export default function ExtendedForecastCard({
forecast, forecast,
}: { forecast: ExtendedForecast[] }) { }: {
forecast: ExtendedForecast[];
}) {
const { isMobile } = useCommonHooks(); const { isMobile } = useCommonHooks();
const day = forecast.slice(2, 14).filter((item: ExtendedForecast) => item.isDaytime); const day = forecast.slice(2, 14).filter((item: ExtendedForecast) => item.isDaytime);
const night = forecast.slice(2, 14).filter((item: ExtendedForecast) => !item.isDaytime); const night = forecast.slice(2, 14).filter((item: ExtendedForecast) => !item.isDaytime);

View File

@@ -1,4 +1,4 @@
import { Stack, Text } from '@mantine/core'; import { Stack } from '@mantine/core';
import { import {
Area, Area,
AreaChart, AreaChart,
@@ -12,34 +12,15 @@ import {
YAxis, YAxis,
} from 'recharts'; } from 'recharts';
import type { HourlyForecast } from 'types'; import type { HourlyForecast } from 'types';
import WeatherTooltip from './WeatherTooltip';
export default function WeatherGraphs({ export default function WeatherGraphs({
hourlyForecast, hourlyForecast,
}: { hourlyForecast: HourlyForecast[] }) { }: {
function CustomTooltip({ payload, active }: any) { hourlyForecast: HourlyForecast[];
if (active) { }) {
return ( const colorPrecipitation = 'colorPrecipitation';
<Stack const colorHumidity = 'colorHumidity';
style={{
backgroundColor: 'var(--mantine-color-background-0)',
padding: '1rem',
borderRadius: '8px',
}}
>
{payload.map((item: any) => (
<Text
key={item.name}
size='md'
c={item.stroke}
>{`${item.name} - ${item.value}${item.unit}`}</Text>
))}
</Stack>
);
}
return null;
}
return ( return (
<Stack <Stack
style={{ style={{
@@ -68,7 +49,7 @@ export default function WeatherGraphs({
}} }}
/> />
<CartesianGrid stroke='#f5f5f522' /> <CartesianGrid stroke='#f5f5f522' />
<Tooltip content={<CustomTooltip />} /> <Tooltip content={<WeatherTooltip />} />
<Line <Line
unit={'°F'} unit={'°F'}
type='monotone' type='monotone'
@@ -103,7 +84,7 @@ export default function WeatherGraphs({
<ResponsiveContainer width={'100%'} height={150}> <ResponsiveContainer width={'100%'} height={150}>
<AreaChart data={hourlyForecast}> <AreaChart data={hourlyForecast}>
<defs> <defs>
<linearGradient id='colorPrecipitation' x1='0' y1='0' x2='0' y2='1'> <linearGradient id={colorPrecipitation} x1='0' y1='0' x2='0' y2='1'>
<stop <stop
offset='40%' offset='40%'
stopColor='var(--mantine-color-primary-0)' stopColor='var(--mantine-color-primary-0)'
@@ -115,7 +96,7 @@ export default function WeatherGraphs({
stopOpacity={0} stopOpacity={0}
/> />
</linearGradient> </linearGradient>
<linearGradient id='colorHumidity' x1='0' y1='0' x2='0' y2='1'> <linearGradient id={colorHumidity} x1='0' y1='0' x2='0' y2='1'>
<stop <stop
offset='20%' offset='20%'
stopColor='var(--mantine-color-accent-0)' stopColor='var(--mantine-color-accent-0)'
@@ -140,7 +121,7 @@ export default function WeatherGraphs({
/> />
<YAxis stroke={'var(--mantine-color-primary-0)'} unit={'%'} domain={[0, 100]} /> <YAxis stroke={'var(--mantine-color-primary-0)'} unit={'%'} domain={[0, 100]} />
<CartesianGrid stroke='#f5f5f522' /> <CartesianGrid stroke='#f5f5f522' />
<Tooltip content={<CustomTooltip />} /> <Tooltip content={<WeatherTooltip />} />
<Area <Area
unit={'%'} unit={'%'}
type='monotone' type='monotone'
@@ -150,7 +131,7 @@ export default function WeatherGraphs({
}} }}
stroke={'var(--mantine-color-primary-0)'} stroke={'var(--mantine-color-primary-0)'}
strokeWidth={2} strokeWidth={2}
fill={'url(#colorPrecipitation)'} fill={`url(#${colorPrecipitation})`}
dot={{ dot={{
stroke: 'var(--mantine-color-primary-0)', stroke: 'var(--mantine-color-primary-0)',
strokeWidth: 0, strokeWidth: 0,
@@ -166,7 +147,7 @@ export default function WeatherGraphs({
}} }}
stroke={'var(--mantine-color-accent-0)'} stroke={'var(--mantine-color-accent-0)'}
strokeWidth={2} strokeWidth={2}
fill={'url(#colorHumidity)'} fill={`url(#${colorHumidity})`}
dot={{ dot={{
stroke: 'var(--mantine-color-accent-0)', stroke: 'var(--mantine-color-accent-0)',
strokeWidth: 0, strokeWidth: 0,

View File

@@ -15,259 +15,167 @@ export default function WeatherIcon({ textDescription }: { textDescription: stri
? textDescription.substring(0, textDescription.trim().indexOf('then')) ? textDescription.substring(0, textDescription.trim().indexOf('then'))
: textDescription; : textDescription;
const Icon = () => { const lookupSource = (icon: any) => {
switch (icon.trim()) { switch (icon.trim()) {
case 'Ash': case 'Ash':
return ( return {
<Image src: HazeIcon,
style={{ width: '75px', height: '75px' }} alt: 'HazeIcon',
src={HazeIcon} };
alt='HazeIcon'
/>
);
case 'Broken Clouds': case 'Broken Clouds':
return ( return {
<Image src: PartlyCloudyIcon,
style={{ width: '75px', height: '75px' }} alt: 'PartlyCloudyIcon',
src={PartlyCloudyIcon} };
alt='PartlyCloudyIcon'
/>
);
case 'Chance Rain Showers': case 'Chance Rain Showers':
return ( return {
<Image src: RainIcon,
style={{ width: '75px', height: '75px' }} alt: 'RainIcon',
src={RainIcon} };
alt='RainIcon'
/>
);
case 'Chance Showers': case 'Chance Showers':
return ( return {
<Image src: RainIcon,
style={{ width: '75px', height: '75px' }} alt: 'RainIcon',
src={RainIcon} };
alt='RainIcon'
/>
);
case 'Slight Chance Showers': case 'Slight Chance Showers':
return ( return {
<Image src: RainIcon,
style={{ width: '75px', height: '75px' }} alt: 'RainIcon',
src={RainIcon} };
alt='RainIcon'
/>
);
case 'Clear':
return (
<Image
style={{ width: '75px', height: '75px' }}
src={SunnyIcon}
alt='SunnyIcon'
/>
);
case 'Cloudy': case 'Cloudy':
return ( return {
<Image src: CloudyIcon,
style={{ width: '75px', height: '75px' }} alt: 'CloudyIcon',
src={CloudyIcon} };
alt='CloudyIcon'
/>
);
case 'Dust': case 'Dust':
return ( return {
<Image src: HazeIcon,
style={{ width: '75px', height: '75px' }} alt: 'HazeIcon',
src={HazeIcon} };
alt='HazeIcon'
/>
);
case 'Drizzle': case 'Drizzle':
return ( return {
<Image src: RainIcon,
style={{ width: '75px', height: '75px' }} alt: 'RainIcon',
src={RainIcon} };
alt='RainIcon'
/>
);
case 'Fog': case 'Fog':
return ( return {
<Image src: HazeIcon,
style={{ width: '75px', height: '75px' }} alt: 'HazeIcon',
src={HazeIcon} };
alt='HazeIcon'
/>
);
case 'Freezing Rain': case 'Freezing Rain':
return ( return {
<Image src: SleetIcon,
style={{ width: '75px', height: '75px' }} alt: 'SleetIcon',
src={SleetIcon} };
alt='SleetIcon'
/>
);
case 'Haze': case 'Haze':
return ( return {
<Image src: HazeIcon,
style={{ width: '75px', height: '75px' }} alt: 'HazeIcon',
src={HazeIcon} };
alt='HazeIcon'
/>
);
case 'Heavy Rain': case 'Heavy Rain':
return ( return {
<Image src: HeavyRainIcon,
style={{ width: '75px', height: '75px' }} alt: 'HeavyRainIcon',
src={HeavyRainIcon} };
alt='HeavyRainIcon'
/>
);
case 'Heavy Snow': case 'Heavy Snow':
return ( return {
<Image src: SnowIcon,
style={{ width: '75px', height: '75px' }} alt: 'SnowIcon',
src={SnowIcon} };
alt='SnowIcon'
/>
);
case 'Light Rain': case 'Light Rain':
return ( return {
<Image src: RainIcon,
style={{ width: '75px', height: '75px' }} alt: 'RainIcon',
src={RainIcon} };
alt='RainIcon'
/>
);
case 'Mist': case 'Mist':
return ( return {
<Image src: HazeIcon,
style={{ width: '75px', height: '75px' }} alt: 'HazeIcon',
src={HazeIcon} };
alt='HazeIcon'
/>
);
case 'Moderate Rain': case 'Moderate Rain':
return ( return {
<Image src: RainIcon,
style={{ width: '75px', height: '75px' }} alt: 'RainIcon',
src={RainIcon} };
alt='RainIcon'
/>
);
case 'Mostly Cloudy': case 'Mostly Cloudy':
return ( return {
<Image src: CloudyIcon,
style={{ width: '75px', height: '75px' }} alt: 'CloudyIcon',
src={CloudyIcon} };
alt='CloudyIcon'
/>
);
case 'Overcast Clouds': case 'Overcast Clouds':
return ( return {
<Image src: PartlyCloudyIcon,
style={{ width: '75px', height: '75px' }} alt: 'PartlyCloudyIcon',
src={PartlyCloudyIcon} };
alt='PartlyCloudyIcon'
/>
);
case 'Patchy Fog': case 'Patchy Fog':
return ( return {
<Image src: HazeIcon,
style={{ width: '75px', height: '75px' }} alt: 'HazeIcon',
src={HazeIcon} };
alt='HazeIcon'
/>
);
case 'Partly Sunny': case 'Partly Sunny':
return ( return {
<Image src: PartlyCloudyIcon,
style={{ width: '75px', height: '75px' }} alt: 'PartlyCloudyIcon',
src={PartlyCloudyIcon} };
alt='PartlyCloudyIcon'
/>
);
case 'Rain': case 'Rain':
return ( return {
<Image src: RainIcon,
style={{ width: '75px', height: '75px' }} alt: 'RainIcon',
src={RainIcon} };
alt='RainIcon'
/>
);
case 'Scattered Clouds': case 'Scattered Clouds':
return ( return {
<Image src: PartlyCloudyIcon,
style={{ width: '75px', height: '75px' }} alt: 'PartlyCloudyIcon',
src={PartlyCloudyIcon} };
alt='PartlyCloudyIcon'
/>
);
case 'Showers': case 'Showers':
return ( return {
<Image src: RainIcon,
style={{ width: '75px', height: '75px' }} alt: 'RainIcon',
src={RainIcon} };
alt='RainIcon'
/>
);
case 'Sleet': case 'Sleet':
return ( return {
<Image src: SleetIcon,
style={{ width: '75px', height: '75px' }} alt: 'SleetIcon',
src={SleetIcon} };
alt='SleetIcon'
/>
);
case 'Smoke': case 'Smoke':
return ( return {
<Image src: HazeIcon,
style={{ width: '75px', height: '75px' }} alt: 'HazeIcon',
src={HazeIcon} };
alt='HazeIcon'
/>
);
case 'Snow': case 'Snow':
return ( return {
<Image src: SnowIcon,
style={{ width: '75px', height: '75px' }} alt: 'SnowIcon',
src={SnowIcon} };
alt='SnowIcon'
/>
);
case 'Squall': case 'Squall':
return ( return {
<Image src: WindyIcon,
style={{ width: '75px', height: '75px' }} alt: 'WindyIcon',
src={WindyIcon} };
alt='WindyIcon'
/>
);
case 'Thunderstorm': case 'Thunderstorm':
return ( return {
<Image src: ThunderstormIcon,
style={{ width: '75px', height: '75px' }} alt: 'ThunderstormIcon',
src={ThunderstormIcon} };
alt='ThunderstormIcon'
/>
);
case 'Tornado': case 'Tornado':
return ( return {
<Image src: WindyIcon,
style={{ width: '75px', height: '75px' }} alt: 'WindyIcon',
src={WindyIcon} };
alt='WindyIcon'
/>
);
default: default:
return ( return {
<Image src: SunnyIcon,
style={{ width: '75px', height: '75px' }} alt: 'SunnyIcon',
src={SunnyIcon} };
alt='SunnyIcon'
/>
);
} }
}; };
return <Icon />; return (
<Image
style={{ width: '75px', height: '75px' }}
src={lookupSource(icon).src}
alt={lookupSource(icon).alt}
/>
);
} }

View File

@@ -1,8 +1,7 @@
import { Center, Group, Modal, Stack, Text } from '@mantine/core'; import { Center, Group, Modal, Stack, Text } from '@mantine/core';
import { modalClasses } from 'styles';
import { useCommonHooks, useWeatherAPI } from 'hooks'; import { useCommonHooks, useWeatherAPI } from 'hooks';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { modalClasses } from 'styles';
import type { CurrentWeather as CW, ExtendedForecast, HourlyForecast } from 'types'; import type { CurrentWeather as CW, ExtendedForecast, HourlyForecast } from 'types';
import CurrentWeather from './CurrentWeather'; import CurrentWeather from './CurrentWeather';

View File

@@ -0,0 +1,27 @@
import { Stack, Text } from '@mantine/core';
function WeatherTooltip({ payload, active }: any) {
if (active) {
return (
<Stack
style={{
backgroundColor: 'var(--mantine-color-background-0)',
padding: '1rem',
borderRadius: '8px',
}}
>
{payload.map((item: any) => (
<Text
key={item.name}
size='md'
c={item.stroke}
>{`${item.name} - ${item.value}${item.unit}`}</Text>
))}
</Stack>
);
}
return null;
}
export default WeatherTooltip;

View File

@@ -1,4 +1,4 @@
export { HelpModal } from './HelpModal/HelpModal'; export { HelpModal } from './HelpModal/HelpModal';
export { WeatherModal } from './WeatherModal/WeatherModal';
export { MainLayout } from './MainLayout';
export { Loading } from './Loading'; export { Loading } from './Loading';
export { MainLayout } from './MainLayout';
export { WeatherModal } from './WeatherModal/WeatherModal';

View File

@@ -1,7 +1,7 @@
export { export {
useQueryAPI,
useWeatherAPI,
useMutationAPI, useMutationAPI,
useOSMAPI, useOSMAPI,
useQueryAPI,
useWeatherAPI,
} from './useAPI'; } from './useAPI';
export { useCommonHooks } from './useCommonHooks'; export { useCommonHooks } from './useCommonHooks';

View File

@@ -6,7 +6,10 @@ import BookmarkItem from './BookmarkItem';
function BookmarkGroups({ function BookmarkGroups({
bookmarks, bookmarks,
category, category,
}: { bookmarks: Bookmark[]; category: string }) { }: {
bookmarks: Bookmark[];
category: string;
}) {
return ( return (
<Stack <Stack
style={{ style={{

View File

@@ -1,17 +1,9 @@
import { import { type OptionsFilter, Select, Stack, Text } from '@mantine/core';
ActionIcon,
Group,
type OptionsFilter,
Select,
Stack,
Text,
} from '@mantine/core';
import { useDisclosure } from '@mantine/hooks'; import { useDisclosure } from '@mantine/hooks';
import { HelpModal, WeatherModal } from 'components'; import { HelpModal, WeatherModal } from 'components';
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks'; import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { FiEdit, FiTrash } from 'react-icons/fi'; import { selectClasses } from 'styles';
import { actionIconClasses, selectClasses } from 'styles';
import type { import type {
Application, Application,
CurrentWeather, CurrentWeather,
@@ -19,6 +11,7 @@ import type {
Provider, Provider,
SearchHistoryItem, SearchHistoryItem,
} from 'types'; } from 'types';
import SearchSelectItem from './SearchHistoryItem';
export default function Search({ export default function Search({
current, current,
@@ -53,7 +46,6 @@ export default function Search({
const [dropdownOpened, { open: openDropdown, close: closeDropdown }] = useDisclosure(); const [dropdownOpened, { open: openDropdown, close: closeDropdown }] = useDisclosure();
const [listIndex, setListIndex] = useState(0); const [listIndex, setListIndex] = useState(0);
const { const {
theme,
isMobile, isMobile,
urlPattern, urlPattern,
providerLabel, providerLabel,
@@ -272,112 +264,6 @@ export default function Search({
.slice(0, 10); .slice(0, 10);
}; };
function RenderOption(item: any) {
const [showFillButton, setShowFillButton] = useState(false);
const m = new Date(item.option.dateTimeStamp);
const year = `${m.getUTCFullYear()}`.slice(-2);
const month = `0${m.getUTCMonth() + 1}`.slice(-2);
const day = `0${m.getUTCDate()}`.slice(-2);
const hours = `0${m.getUTCHours()}`.slice(-2);
const minutes = `0${m.getUTCMinutes()}`.slice(-2);
const dateString = isMobile
? `${month}/${day}/${year}`
: `${month}/${day}/${year} ${hours}:${minutes}`;
return (
<Group
w={'100%'}
display={'flex'}
justify={'space-between'}
gap={0}
onClick={() => {
handleSearchHistoryItem(item);
}}
onMouseEnter={() => {
setShowFillButton(true);
}}
onMouseLeave={() => {
setShowFillButton(false);
}}
>
<Group m={0} p={0} miw={'160px'} style={{ overflow: 'hidden' }}>
<Text
style={{
backgroundColor: `${theme.primaryColor}33`,
fontWeight: 600,
borderRadius: '15px',
padding: '.25rem',
}}
>
{item.option.provider}
</Text>
{showFillButton && (
<ActionIcon
className={actionIconClasses.selectOptionActions}
classNames={actionIconClasses}
onClick={(e) => {
e.stopPropagation();
setSearchValue(`${item.option.prefix} ${item.option.searchTerm}`);
}}
>
<FiEdit />
</ActionIcon>
)}
</Group>
<Group
m={0}
p={0}
gap={0}
justify='space-between'
w={'65%'}
style={{
whiteSpace: 'nowrap',
overflow: 'hidden',
boxSizing: 'border-box',
position: 'relative',
}}
>
<Text
w={'75%'}
c={'var(--mantine-color-primary-0)'}
style={{
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{item.option.searchTerm}
</Text>
<Text
w={'25%'}
style={{
color: `${theme.primaryColor}55`,
textAlign: 'right',
}}
>
{dateString}
</Text>
</Group>
<Group m={0} p={0} right={'1rem'} w={'24px'}>
{showFillButton && (
<ActionIcon
className={actionIconClasses.selectOptionActions}
classNames={actionIconClasses}
onClick={(e) => {
e.stopPropagation();
mutateAsync({
method: 'DELETE',
endpoint: searchEndpoint,
body: { id: item.option.id },
});
}}
>
<FiTrash />
</ActionIcon>
)}
</Group>
</Group>
);
}
useEffect(() => { useEffect(() => {
if (providerData && providerDataIsFetched) { if (providerData && providerDataIsFetched) {
setProviders(providerData); setProviders(providerData);
@@ -460,7 +346,13 @@ export default function Search({
autoFocus={!isMobile} autoFocus={!isMobile}
leftSection={providerLabel} leftSection={providerLabel}
rightSection={<Stack />} rightSection={<Stack />}
renderOption={RenderOption} renderOption={(item: any) => (
<SearchSelectItem
item={item}
handleSearchHistoryItem={handleSearchHistoryItem}
searchEndpoint={searchEndpoint}
/>
)}
onBlur={() => { onBlur={() => {
setPlaceholderValue(''); setPlaceholderValue('');
setListIndex(0); setListIndex(0);

View File

@@ -0,0 +1,115 @@
import { ActionIcon, Group, Text } from '@mantine/core';
import { useCommonHooks, useMutationAPI } from 'hooks';
import { useState } from 'react';
import { FiEdit, FiTrash } from 'react-icons/fi';
import { actionIconClasses } from 'styles';
function SearchSelectItem({ item, handleSearchHistoryItem, searchEndpoint }: any) {
const { isMobile, theme, setSearchValue } = useCommonHooks();
const { mutateAsync } = useMutationAPI();
const [showFillButton, setShowFillButton] = useState(false);
const m = new Date(item.option.dateTimeStamp);
const year = `${m.getUTCFullYear()}`.slice(-2);
const month = `0${m.getUTCMonth() + 1}`.slice(-2);
const day = `0${m.getUTCDate()}`.slice(-2);
const hours = `0${m.getUTCHours()}`.slice(-2);
const minutes = `0${m.getUTCMinutes()}`.slice(-2);
const dateString = isMobile
? `${month}/${day}/${year}`
: `${month}/${day}/${year} ${hours}:${minutes}`;
return (
<Group
w={'100%'}
display={'flex'}
justify={'space-between'}
gap={0}
onClick={() => {
handleSearchHistoryItem(item);
}}
onMouseEnter={() => {
setShowFillButton(true);
}}
onMouseLeave={() => {
setShowFillButton(false);
}}
>
<Group m={0} p={0} miw={'160px'} style={{ overflow: 'hidden' }}>
<Text
style={{
backgroundColor: `${theme.primaryColor}33`,
fontWeight: 600,
borderRadius: '15px',
padding: '.25rem',
}}
>
{item.option.provider}
</Text>
{showFillButton && (
<ActionIcon
className={actionIconClasses.selectOptionActions}
classNames={actionIconClasses}
onClick={(e) => {
e.stopPropagation();
setSearchValue(`${item.option.prefix} ${item.option.searchTerm}`);
}}
>
<FiEdit />
</ActionIcon>
)}
</Group>
<Group
m={0}
p={0}
gap={0}
justify='space-between'
w={'65%'}
style={{
whiteSpace: 'nowrap',
overflow: 'hidden',
boxSizing: 'border-box',
position: 'relative',
}}
>
<Text
w={'75%'}
c={'var(--mantine-color-primary-0)'}
style={{
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{item.option.searchTerm}
</Text>
<Text
w={'25%'}
style={{
color: `${theme.primaryColor}55`,
textAlign: 'right',
}}
>
{dateString}
</Text>
</Group>
<Group m={0} p={0} right={'1rem'} w={'24px'}>
{showFillButton && (
<ActionIcon
className={actionIconClasses.selectOptionActions}
classNames={actionIconClasses}
onClick={(e) => {
e.stopPropagation();
mutateAsync({
method: 'DELETE',
endpoint: searchEndpoint,
body: { id: item.option.id },
});
}}
>
<FiTrash />
</ActionIcon>
)}
</Group>
</Group>
);
}
export default SearchSelectItem;

View File

@@ -23,7 +23,11 @@ function DebouncedApplication({
application, application,
setIsOpen, setIsOpen,
allIcons, allIcons,
}: { application: Application; setIsOpen: (isOpen: boolean) => void; allIcons: any }) { }: {
application: Application;
setIsOpen: (isOpen: boolean) => void;
allIcons: any;
}) {
const { handleDelete, isMobile } = useCommonHooks(); const { handleDelete, isMobile } = useCommonHooks();
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();

View File

@@ -10,6 +10,7 @@ import AddItemModal from '../Shared/AddItemModal';
import NoItemsFound from '../Shared/NoItemsFound'; import NoItemsFound from '../Shared/NoItemsFound';
import SettingsHeader from '../Shared/SettingsHeader'; import SettingsHeader from '../Shared/SettingsHeader';
import DebouncedGroupSettingsItem from './DebouncedGroupSettingsItem'; import DebouncedGroupSettingsItem from './DebouncedGroupSettingsItem';
const BookmarkSettings = () => { const BookmarkSettings = () => {
const defaultGroup = { const defaultGroup = {
id: null, id: null,

View File

@@ -10,7 +10,10 @@ import DeleteItemModal from '../Shared/DeleteItemModal';
function DebouncedBookmarkItem({ function DebouncedBookmarkItem({
bookmarkItem, bookmarkItem,
setIsOpen, setIsOpen,
}: { bookmarkItem: Bookmark; setIsOpen: (isOpen: boolean) => void }) { }: {
bookmarkItem: Bookmark;
setIsOpen: (isOpen: boolean) => void;
}) {
const { handleDelete, isMobile } = useCommonHooks(); const { handleDelete, isMobile } = useCommonHooks();
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();

View File

@@ -33,7 +33,11 @@ function DebouncedGroupSettingsItem({
bookmarkGroup, bookmarkGroup,
bookmarks, bookmarks,
searchTerm, searchTerm,
}: { bookmarkGroup: BookmarkGroup; bookmarks: Bookmark[]; searchTerm: string }) { }: {
bookmarkGroup: BookmarkGroup;
bookmarks: Bookmark[];
searchTerm: string;
}) {
const { isMobile, handleDelete, reorder } = useCommonHooks(); const { isMobile, handleDelete, reorder } = useCommonHooks();
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
const [popoverOpened, setPopoverOpened] = useState(false); const [popoverOpened, setPopoverOpened] = useState(false);

View File

@@ -5,6 +5,7 @@ import { useEffect, useState } from 'react';
import { cardClasses, textInputClasses } from 'styles'; import { cardClasses, textInputClasses } from 'styles';
import type { PersonalData } from 'types'; import type { PersonalData } from 'types';
import SettingsHeader from '../Shared/SettingsHeader'; import SettingsHeader from '../Shared/SettingsHeader';
const PersonalizationSettings = () => { const PersonalizationSettings = () => {
const endpoint = 'api/personalData'; const endpoint = 'api/personalData';
const { const {

View File

@@ -11,7 +11,7 @@ import {
} from '@mantine/core'; } from '@mantine/core';
import { useCommonHooks } from 'hooks'; import { useCommonHooks } from 'hooks';
import { useState } from 'react'; import { useState } from 'react';
import { FiHelpCircle, FiLoader, FiPlus } from 'react-icons/fi'; import { FiHelpCircle, FiLoader, FiPlus, FiX } from 'react-icons/fi';
import { import {
actionIconClasses, actionIconClasses,
buttonClasses, buttonClasses,
@@ -137,13 +137,23 @@ function SettingsHeader({
zIndex: 100, zIndex: 100,
}} }}
rightSection={ rightSection={
isLoading && ( isLoading ? (
<ActionIcon <ActionIcon
className={actionIconClasses.loadingSpinner} className={actionIconClasses.loadingSpinner}
classNames={actionIconClasses} classNames={actionIconClasses}
> >
<FiLoader /> <FiLoader />
</ActionIcon> </ActionIcon>
) : (
searchValue && (
<ActionIcon
className={actionIconClasses.settingsTooltip}
classNames={actionIconClasses}
onClick={() => setSearchValue('')}
>
<FiX />
</ActionIcon>
)
) )
} }
/> />

View File

@@ -0,0 +1,69 @@
import { Box, Stack, Text } from '@mantine/core';
import { useState } from 'react';
import { FiCheck } from 'react-icons/fi';
import { stackClasses } from 'styles';
import type { Theme } from 'types';
function ExampleApp({ selectedTheme }: { selectedTheme: Theme }) {
const [textDecoration, setTextDecoration] = useState<string>('none');
return (
<Box
component='a'
onMouseOver={() => setTextDecoration('underline')}
onMouseOut={() => setTextDecoration('none')}
className={stackClasses.appItemBox}
>
<Stack
style={{
height: '64px',
marginRight: '1rem',
marginLeft: '1rem',
paddingTop: '1rem',
}}
>
<FiCheck style={{ color: selectedTheme.primaryColor }} height={32} width={32} />
</Stack>
<Stack
style={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
flex: 1,
overflow: 'hidden',
gap: 0,
}}
>
<Text
style={{
fontWeight: 400,
fontSize: '.625rem',
fontStyle: 'normal',
color: selectedTheme.primaryColor,
fontFamily:
'-apple-system, Jost, BlinkMacSystemFont, Helvetica Neue, Roboto, sans-serif !important',
margin: 0,
}}
>
{'Example App'}
</Text>
<Text
style={{
fontWeight: 400,
fontSize: '.625rem',
fontStyle: 'italic',
color: selectedTheme.accentColor,
fontFamily:
'-apple-system, Jost, BlinkMacSystemFont, Helvetica Neue, Roboto, sans-serif !important',
margin: 0,
textDecoration: textDecoration,
}}
>
{'https://www.example.com'}
</Text>
</Stack>
</Box>
);
}
export default ExampleApp;

View File

@@ -1,12 +1,7 @@
import { Group, Text } from '@mantine/core'; import { Group, Text } from '@mantine/core';
import type { Theme } from 'types'; import type { Theme } from 'types';
function ThemeMiniCard({
allThemes, function ThemeMiniCard({ allThemes, item }: { allThemes: Theme[]; item: number }) {
item,
}: {
allThemes: Theme[];
item: number;
}) {
const theme = allThemes?.find((theme: Theme) => theme.id === item); const theme = allThemes?.find((theme: Theme) => theme.id === item);
return ( return (
@@ -19,9 +14,10 @@ function ThemeMiniCard({
w={'175px'} w={'175px'}
h={'60px'} h={'60px'}
style={{ style={{
background: `linear-gradient(${theme?.backgroundColor}33, ${theme?.backgroundColor}33), no-repeat center url(/backgrounds/${theme?.backgroundImage})`, backgroundImage: `linear-gradient(${theme?.backgroundColor}33, ${theme?.backgroundColor}33), url(/backgrounds/${theme?.backgroundImage})`,
backgroundSize: 'cover', backgroundSize: 'cover',
backgroundColor: theme?.backgroundColor, backgroundColor: theme?.backgroundColor,
backgroundPosition: 'center',
border: `solid ${theme?.borderColor}`, border: `solid ${theme?.borderColor}`,
borderRadius: '8px', borderRadius: '8px',
}} }}

View File

@@ -1,84 +1,22 @@
import { Box, Group, Stack, Text } from '@mantine/core'; import { Group, Stack, Text } from '@mantine/core';
import { useState } from 'react';
import { FiCheck } from 'react-icons/fi';
import { stackClasses } from 'styles';
import type { Theme } from 'types'; import type { Theme } from 'types';
import ThemeExampleApp from './ThemeExampleApp';
export default function ThemeModalContent({ selectedTheme }: { selectedTheme: Theme }) { export default function ThemeModalContent({ selectedTheme }: { selectedTheme: Theme }) {
const ExampleApp = () => {
const [textDecoration, setTextDecoration] = useState<string>('none');
return (
<Box
component='a'
onMouseOver={() => setTextDecoration('underline')}
onMouseOut={() => setTextDecoration('none')}
className={stackClasses.appItemBox}
>
<Stack
style={{
height: '64px',
marginRight: '1rem',
marginLeft: '1rem',
paddingTop: '1rem',
}}
>
<FiCheck style={{ color: selectedTheme.primaryColor }} height={32} width={32} />
</Stack>
<Stack
style={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
flex: 1,
overflow: 'hidden',
gap: 0,
}}
>
<Text
style={{
fontWeight: 400,
fontSize: '.625rem',
fontStyle: 'normal',
color: selectedTheme.primaryColor,
fontFamily:
'-apple-system, Jost, BlinkMacSystemFont, Helvetica Neue, Roboto, sans-serif !important',
margin: 0,
}}
>
{'Example App'}
</Text>
<Text
style={{
fontWeight: 400,
fontSize: '.625rem',
fontStyle: 'italic',
color: selectedTheme.accentColor,
fontFamily:
'-apple-system, Jost, BlinkMacSystemFont, Helvetica Neue, Roboto, sans-serif !important',
margin: 0,
textDecoration: textDecoration,
}}
>
{'https://www.example.com'}
</Text>
</Stack>
</Box>
);
};
return ( return (
<Stack <Stack
style={{ style={{
width: '100%', width: '100%',
background: backgroundImage:
selectedTheme.backgroundImage !== 'none' selectedTheme.backgroundImage !== 'none'
? `linear-gradient(${selectedTheme.backgroundColor}33, ${selectedTheme.backgroundColor}33), no-repeat center url(/backgrounds/${selectedTheme.backgroundImage})` ? `linear-gradient(${selectedTheme?.backgroundColor}33, ${selectedTheme?.backgroundColor}33), url(/backgrounds/${selectedTheme?.backgroundImage})`
: undefined, : undefined,
backgroundSize: selectedTheme.backgroundImage !== 'none' ? 'cover' : undefined, backgroundSize: selectedTheme.backgroundImage !== 'none' ? 'cover' : undefined,
backgroundColor: backgroundColor:
selectedTheme.backgroundImage === 'none' selectedTheme.backgroundImage === 'none'
? selectedTheme.backgroundColor ? selectedTheme.backgroundColor
: undefined, : undefined,
backgroundPosition: 'center',
borderRadius: '10px', borderRadius: '10px',
border: '2px solid', border: '2px solid',
}} }}
@@ -124,9 +62,9 @@ export default function ThemeModalContent({ selectedTheme }: { selectedTheme: Th
alignContent: 'center', alignContent: 'center',
}} }}
> >
<ExampleApp /> <ThemeExampleApp selectedTheme={selectedTheme} />
<ExampleApp /> <ThemeExampleApp selectedTheme={selectedTheme} />
<ExampleApp /> <ThemeExampleApp selectedTheme={selectedTheme} />
</Stack> </Stack>
<Text <Text
pl={'1.5rem'} pl={'1.5rem'}

View File

@@ -57,8 +57,10 @@ function ThemePlaylists() {
useEffect(() => { useEffect(() => {
if (themePlaylistData && isThemePlaylistDataFetched) { if (themePlaylistData && isThemePlaylistDataFetched) {
setThemePlaylists( setThemePlaylists(
themePlaylistData.filter((a: ThemePlaylist) => themePlaylistData.filter(
a.name.toLowerCase().includes(debouncedSearch.toLowerCase()), (a: ThemePlaylist) =>
a.name.toLowerCase().includes(debouncedSearch.toLowerCase()) ||
a.name.includes('Time of Day'),
), ),
); );
} }
@@ -95,7 +97,7 @@ function ThemePlaylists() {
isLoading={isLoading} isLoading={isLoading}
setIsLoading={setIsLoading} setIsLoading={setIsLoading}
/> />
{allThemeData ? ( {allThemeData && (
<> <>
<Group mt={'6rem'} justify='center' align='center' pos={'relative'}> <Group mt={'6rem'} justify='center' align='center' pos={'relative'}>
<Text <Text
@@ -293,8 +295,6 @@ function ThemePlaylists() {
)} )}
</Group> </Group>
</> </>
) : (
<></>
)} )}
</Stack> </Stack>
</Stack> </Stack>

View File

@@ -55,7 +55,6 @@ function ThemesSettings() {
currentThemePlaylist, currentThemePlaylist,
setCurrentThemePlaylist, setCurrentThemePlaylist,
} = useCommonHooks(); } = useCommonHooks();
const handleOpen = (theme: Theme) => { const handleOpen = (theme: Theme) => {
setSelectedTheme(theme); setSelectedTheme(theme);
open(); open();
@@ -267,7 +266,7 @@ function ThemesSettings() {
navigate(`/${import.meta.env.VITE_BASE_URL}/settings/themePlaylists`); navigate(`/${import.meta.env.VITE_BASE_URL}/settings/themePlaylists`);
} else { } else {
setCurrentThemePlaylist('All'); setCurrentThemePlaylist('All');
setCurrentTheme('random'); setCurrentTheme(theme.name);
} }
}} }}
> >

View File

@@ -1,18 +1,18 @@
export type { export type { AppCategory, Application } from './application';
CurrentWeather,
OutlookWeather,
ExtendedForecast,
HourlyForecast,
SunState,
} from './weather';
export type { Application, AppCategory } from './application';
export type { Bookmark } from './bookmark'; export type { Bookmark } from './bookmark';
export type { BookmarkGroup } from './bookmarkGroup'; export type { BookmarkGroup } from './bookmarkGroup';
export type { PersonalData } from './personalData';
export type { Message } from './message';
export type { Provider } from './provider';
export type { Theme, ThemePlaylist } from './theme';
export type { WolClient } from './wolClient';
export type { SearchHistoryItem, SearchShortcut } from './search';
export type { User } from './user';
export type { Location } from './location'; export type { Location } from './location';
export type { Message } from './message';
export type { PersonalData } from './personalData';
export type { Provider } from './provider';
export type { SearchHistoryItem, SearchShortcut } from './search';
export type { Theme, ThemePlaylist } from './theme';
export type { User } from './user';
export type {
CurrentWeather,
ExtendedForecast,
HourlyForecast,
OutlookWeather,
SunState,
} from './weather';
export type { WolClient } from './wolClient';