From d36cec3d31aaa5958444cd51da14a96ff53877f0 Mon Sep 17 00:00:00 2001 From: Nikurasu Date: Sat, 24 Aug 2024 22:51:13 +0200 Subject: [PATCH] feature(settingsPage): save links to localstorage --- package.json | 2 + pnpm-lock.yaml | 17 ++ src/index.tsx | 5 +- src/shared/classes/HomeplageLink.ts | 16 ++ src/shared/components/AppSnackBar.tsx | 41 +++++ src/shared/components/LinkList.tsx | 32 ++++ src/shared/components/settingsPage.tsx | 151 ++++++++++++------ .../contexts/ConfiguarableLinksContext.tsx | 10 +- src/shared/contexts/DrawerOpenContext.tsx | 1 - src/shared/contexts/SnackBarValuesConext.tsx | 25 +++ ...pageLink.interface.ts => IHomepageLink.ts} | 2 +- src/shared/interfaces/ISnackBarValues.ts | 4 + src/site/App.tsx | 17 +- 13 files changed, 252 insertions(+), 71 deletions(-) create mode 100644 src/shared/classes/HomeplageLink.ts create mode 100644 src/shared/components/AppSnackBar.tsx create mode 100644 src/shared/components/LinkList.tsx create mode 100644 src/shared/contexts/SnackBarValuesConext.tsx rename src/shared/interfaces/{HomepageLink.interface.ts => IHomepageLink.ts} (65%) create mode 100644 src/shared/interfaces/ISnackBarValues.ts diff --git a/package.json b/package.json index d8a09f2..6512352 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,11 @@ "@mui/icons-material": "^5.16.4", "@mui/material": "^5.16.4", "@mui/styled-engine-sc": "6.0.0-alpha.18", + "@types/uuid": "^10.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", "styled-components": "^6.1.12", + "uuid": "^10.0.0", "wouter": "^3.3.1" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3a6fcb4..a57a22f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: '@mui/styled-engine-sc': specifier: 6.0.0-alpha.18 version: 6.0.0-alpha.18(styled-components@6.1.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + '@types/uuid': + specifier: ^10.0.0 + version: 10.0.0 react: specifier: ^18.2.0 version: 18.3.1 @@ -35,6 +38,9 @@ importers: styled-components: specifier: ^6.1.12 version: 6.1.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + uuid: + specifier: ^10.0.0 + version: 10.0.0 wouter: specifier: ^3.3.1 version: 3.3.1(react@18.3.1) @@ -651,6 +657,9 @@ packages: '@types/stylis@4.2.5': resolution: {integrity: sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==} + '@types/uuid@10.0.0': + resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} + '@types/webextension-polyfill@0.10.7': resolution: {integrity: sha512-10ql7A0qzBmFB+F+qAke/nP1PIonS0TXZAOMVOxEUsm+lGSW6uwVcISFNa0I4Oyj0884TZVWGGMIWeXOVSNFHw==} @@ -1682,6 +1691,10 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + uuid@10.0.0: + resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + hasBin: true + uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true @@ -2361,6 +2374,8 @@ snapshots: '@types/stylis@4.2.5': {} + '@types/uuid@10.0.0': {} + '@types/webextension-polyfill@0.10.7': {} '@vitejs/plugin-react@4.3.1(vite@5.3.4(@types/node@20.14.11))': @@ -3385,6 +3400,8 @@ snapshots: util-deprecate@1.0.2: {} + uuid@10.0.0: {} + uuid@8.3.2: {} vite-plugin-web-extension@4.1.6(@types/node@20.14.11): diff --git a/src/index.tsx b/src/index.tsx index 02ed79b..eb08771 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -11,6 +11,7 @@ import theme from './theme'; import { DrawerOpenContextProvider } from './shared/contexts/DrawerOpenContext'; import { CurrentPageContextProvider } from './shared/contexts/CurrentPageContext'; import { ConfigurableLinksContextProvider } from './shared/contexts/ConfiguarableLinksContext'; +import { SnackBarValuesContextProvider } from './shared/contexts/SnackBarValuesConext'; ReactDOM.createRoot(document.getElementById('root')!).render( @@ -19,7 +20,9 @@ ReactDOM.createRoot(document.getElementById('root')!).render( - + + + diff --git a/src/shared/classes/HomeplageLink.ts b/src/shared/classes/HomeplageLink.ts new file mode 100644 index 0000000..5424551 --- /dev/null +++ b/src/shared/classes/HomeplageLink.ts @@ -0,0 +1,16 @@ +import { IHomepageLink } from "../interfaces/IHomepageLink"; +import * as uuid from 'uuid'; + +export class HomepageLink implements IHomepageLink { + title: string; + subtitle: string; + hyperlink: string; + uuid: string + + constructor() { + this.title = "" + this.subtitle = "" + this.hyperlink = "" + this.uuid = uuid.v4() + } +} \ No newline at end of file diff --git a/src/shared/components/AppSnackBar.tsx b/src/shared/components/AppSnackBar.tsx new file mode 100644 index 0000000..56dfeb7 --- /dev/null +++ b/src/shared/components/AppSnackBar.tsx @@ -0,0 +1,41 @@ +import { useContext } from "react"; +import { SnackBarValuesContext } from "../contexts/SnackBarValuesConext"; +import { IconButton, Snackbar, SnackbarCloseReason } from "@mui/material"; +import CloseIcon from '@mui/icons-material/Close'; + +export default function() { + const {snackBarValues, setSnackBarValues} = useContext(SnackBarValuesContext) + + const handleClose = ( + event: React.SyntheticEvent | Event, + reason?: SnackbarCloseReason + ) => { + if (reason === 'clickaway') { + return + } + setSnackBarValues({open: false, message: ""}) + } + + const action = ( + <> + + + + + ) + + return ( + + ) +} \ No newline at end of file diff --git a/src/shared/components/LinkList.tsx b/src/shared/components/LinkList.tsx new file mode 100644 index 0000000..ce62937 --- /dev/null +++ b/src/shared/components/LinkList.tsx @@ -0,0 +1,32 @@ +import Sites from '@Data/sites.json' +import { Grid } from "@mui/material"; +import LinkCard from "./linkcard"; +import { useContext } from 'react'; +import { ConfigurableLinksContext } from '../contexts/ConfiguarableLinksContext'; + +export default function LinkList() { + const {configurableLinks, setConfigurableLinks} = useContext(ConfigurableLinksContext) + + return ( + + {Sites.sites.map(site => ( + + + + ))} + {configurableLinks.map(link => ( + + + + ))} + + ) +} \ No newline at end of file diff --git a/src/shared/components/settingsPage.tsx b/src/shared/components/settingsPage.tsx index 5e26f71..7847eb3 100644 --- a/src/shared/components/settingsPage.tsx +++ b/src/shared/components/settingsPage.tsx @@ -1,69 +1,120 @@ import { Button, Grid, styled, TextField, Typography } from "@mui/material"; -import { useContext, useState } from "react"; -import { HomepageLink } from "../interfaces/HomepageLink.interface"; -import { ConfigurableLinksContext } from "../contexts/ConfiguarableLinksContext"; - -const FormGrid = styled(Grid)(() => ({ - display: 'flex', - flexDirection: 'column' -})) - -interface FormRowProps { - index: number - link: HomepageLink -} +import { useContext, useEffect, useState } from "react"; +import { IHomepageLink } from "../interfaces/IHomepageLink"; +import { HomepageLink } from "../classes/HomeplageLink"; +import { SnackBarValuesContext } from "../contexts/SnackBarValuesConext"; export default function SettingsPage() { - const {configurableLinks, setConfigurableLinks} = useContext(ConfigurableLinksContext) + const [configurableLinks, setConfigurableLinks] = useState>( + JSON.parse(localStorage.getItem("configurableLinks") as string) as Array ?? + new Array + ) + const {snackBarValues, setSnackBarValues} = useContext(SnackBarValuesContext) + const handleChange = (index: number, event: React.ChangeEvent) => { let newconfigurableLinks = [...configurableLinks] - newconfigurableLinks[index][event.target.name as keyof HomepageLink] = event.target.value + newconfigurableLinks[index][event.target.name as keyof IHomepageLink] = event.target.value setConfigurableLinks(newconfigurableLinks) } - const addFields = () => { - setConfigurableLinks([...configurableLinks, {} as HomepageLink]) + const handleBlur = () => { + if (LinkNotEmpty(configurableLinks[configurableLinks.length -1])) { + addFields() + } + if (configurableLinks.length > 1) { + if (LinkEmpty(configurableLinks[configurableLinks.length -2])) { + deleteFieldsAt(configurableLinks.length -2) + } + } } + function LinkNotEmpty(link: IHomepageLink): Boolean { + return link.title != "" && + link.subtitle != "" && + link.hyperlink != "" + } + + function LinkEmpty(link: IHomepageLink): Boolean { + return link.title == "" && + link.subtitle == "" && + link.hyperlink == "" + } + + const addFields = () => { + setConfigurableLinks([...configurableLinks, new HomepageLink()]) + } + + const deleteFieldsAt = (index: number) => { + setConfigurableLinks(configurableLinks.filter((link) => {return link != configurableLinks[index]})) + } + + const saveToLocalStorage = () => { + localStorage.setItem("configurableLinks", JSON.stringify(configurableLinks.filter((link) => {return link != configurableLinks[configurableLinks.length -1]}))) + setSnackBarValues({open: true, message: "Saved Links"}) + } + + useEffect(() => { + addFields() + return () => { + alert("Will close settings") + } + }, []) + return ( - + {configurableLinks.map((link, index) =>( - <> - - handleChange(index, e)} - /> + + + handleChange(index, e)} + onBlur={handleBlur} + /> + + + handleChange(index, e)} + onBlur={handleBlur} + /> + + + handleChange(index, e)} + onBlur={handleBlur} + /> + + {(configurableLinks.length > 1 && index != configurableLinks.length -1)? ( + + + + ):null} - - handleChange(index, e)} - /> - - - handleChange(index, e)} - /> - - ))} - + ) diff --git a/src/shared/contexts/ConfiguarableLinksContext.tsx b/src/shared/contexts/ConfiguarableLinksContext.tsx index 7637921..45e3236 100644 --- a/src/shared/contexts/ConfiguarableLinksContext.tsx +++ b/src/shared/contexts/ConfiguarableLinksContext.tsx @@ -1,19 +1,19 @@ import { createContext, Dispatch, SetStateAction, useState } from "react" -import { HomepageLink } from "../interfaces/HomepageLink.interface" +import { IHomepageLink } from "../interfaces/IHomepageLink" import { ContainerProps } from "@mui/material" type ConfigurableLinksContextType = { - configurableLinks: Array, - setConfigurableLinks: Dispatch>> + configurableLinks: Array, + setConfigurableLinks: Dispatch>> } const ConfigurableLinksContext = createContext({ - configurableLinks: new Array, + configurableLinks: new Array, setConfigurableLinks: () => {} }) const ConfigurableLinksContextProvider = (props: ContainerProps) => { - const [configurableLinks, setConfigurableLinks] = useState(new Array) + const [configurableLinks, setConfigurableLinks] = useState(new Array) return ( diff --git a/src/shared/contexts/DrawerOpenContext.tsx b/src/shared/contexts/DrawerOpenContext.tsx index 4cc02c6..11f3c2a 100644 --- a/src/shared/contexts/DrawerOpenContext.tsx +++ b/src/shared/contexts/DrawerOpenContext.tsx @@ -1,4 +1,3 @@ - import { ContainerProps } from "@mui/material"; import { createContext, Dispatch, SetStateAction, useState } from "react"; diff --git a/src/shared/contexts/SnackBarValuesConext.tsx b/src/shared/contexts/SnackBarValuesConext.tsx new file mode 100644 index 0000000..7d4c70d --- /dev/null +++ b/src/shared/contexts/SnackBarValuesConext.tsx @@ -0,0 +1,25 @@ +import { createContext, Dispatch, SetStateAction, useState } from "react" +import { ISnackBarValues } from "../interfaces/ISnackBarValues" +import { ContainerProps } from "@mui/material" + +type SnackBarValuesContextType = { + snackBarValues: ISnackBarValues, + setSnackBarValues: Dispatch> +} + +const SnackBarValuesContext = createContext({ + snackBarValues: {open: false, message: ""} as ISnackBarValues, + setSnackBarValues: () => {} +}) + +const SnackBarValuesContextProvider = (props: ContainerProps) => { + const [snackBarValues, setSnackBarValues] = useState({open: false, message: ""}) + + return ( + + {props.children} + + ) +} + +export {SnackBarValuesContext, SnackBarValuesContextProvider} \ No newline at end of file diff --git a/src/shared/interfaces/HomepageLink.interface.ts b/src/shared/interfaces/IHomepageLink.ts similarity index 65% rename from src/shared/interfaces/HomepageLink.interface.ts rename to src/shared/interfaces/IHomepageLink.ts index cc95389..ad899ae 100644 --- a/src/shared/interfaces/HomepageLink.interface.ts +++ b/src/shared/interfaces/IHomepageLink.ts @@ -1,4 +1,4 @@ -export interface HomepageLink { +export interface IHomepageLink { title: string subtitle: string, hyperlink: string diff --git a/src/shared/interfaces/ISnackBarValues.ts b/src/shared/interfaces/ISnackBarValues.ts new file mode 100644 index 0000000..51fdfdf --- /dev/null +++ b/src/shared/interfaces/ISnackBarValues.ts @@ -0,0 +1,4 @@ +export interface ISnackBarValues { + open: boolean + message: string +} \ No newline at end of file diff --git a/src/site/App.tsx b/src/site/App.tsx index e1fb041..fa0f4f1 100644 --- a/src/site/App.tsx +++ b/src/site/App.tsx @@ -3,10 +3,11 @@ import { Box, Button, debounce, FormLabel, Grid, OutlinedInput, styled, TextFiel import React, { useContext, useState } from 'react' import MenuBar from '../shared/components/MenuBar' import LinkCard from '../shared/components/linkcard' -import { Link, Route } from 'wouter' import MenuDrawer from '../shared/components/menudrawer' import { CurrentPageContext } from '../shared/contexts/CurrentPageContext' import SettingsPage from '../shared/components/settingsPage' +import LinkList from '../shared/components/LinkList' +import AppSnackBar from '../shared/components/AppSnackBar' const useLocalStorage = (storageKey: string, fallbackState: any) => { const [value, setValue] = React.useState( @@ -21,7 +22,6 @@ const useLocalStorage = (storageKey: string, fallbackState: any) => { function App() { const {currentPage, setCurrentPage} = useContext(CurrentPageContext) - const [drawerOpen, setDrawerOpen] = useState(false) return ( <> @@ -29,22 +29,13 @@ function App() { {currentPage === 'home' ? ( - - {Sites.sites.map(site => ( - - - - ))} - + ): null} {currentPage === 'edit' ? ( ): null} + ) }