Merge pull request #9 from pawelmalak/qol-improvements

Added favicon and changed page title. Changed message when there are …
This commit is contained in:
pawelmalak 2021-06-09 01:04:26 +02:00 committed by GitHub
commit cf44f45fde
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 136 additions and 60 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -10,36 +10,15 @@
content="Web site created using create-react-app" content="Web site created using create-react-app"
/> />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<link rel="preconnect" href="https://fonts.gstatic.com"> <link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css?family=Roboto:400,500,700,900" rel="stylesheet"> <link href="https://fonts.googleapis.com/css?family=Roboto:400,500,700,900" rel="stylesheet">
<title>React App</title> <title>Flame</title>
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div> <div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body> </body>
</html> </html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -1,25 +0,0 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@ -18,6 +18,10 @@ if (localStorage.theme) {
store.dispatch<any>(setTheme(localStorage.theme)); store.dispatch<any>(setTheme(localStorage.theme));
} }
if (localStorage.customTitle) {
document.title = localStorage.customTitle;
}
const App = (): JSX.Element => { const App = (): JSX.Element => {
return ( return (
<Provider store={store}> <Provider store={store}>

View File

@ -98,7 +98,7 @@ const AppForm = (props: ComponentProps): JSX.Element => {
value={formData.url} value={formData.url}
onChange={(e) => inputChangeHandler(e)} onChange={(e) => inputChangeHandler(e)}
/> />
<span>Use URL without protocol</span> <span>Only urls without http[s]:// are supported</span>
</InputGroup> </InputGroup>
<InputGroup> <InputGroup>
<label htmlFor='icon'>App Icon</label> <label htmlFor='icon'>App Icon</label>

View File

@ -6,6 +6,7 @@ import AppCard from '../AppCard/AppCard';
interface ComponentProps { interface ComponentProps {
apps: App[]; apps: App[];
totalApps?: number;
} }
const AppGrid = (props: ComponentProps): JSX.Element => { const AppGrid = (props: ComponentProps): JSX.Element => {
@ -23,9 +24,15 @@ const AppGrid = (props: ComponentProps): JSX.Element => {
</div> </div>
) )
} else { } else {
apps = ( if (props.totalApps) {
<p className={classes.AppsMessage}>You don't have any applications. You can add a new one from <Link to='/applications'>/application</Link> menu</p> apps = (
); <p className={classes.AppsMessage}>There are no pinned applications. You can pin them from the <Link to='/applications'>/applications</Link> menu</p>
);
} else {
apps = (
<p className={classes.AppsMessage}>You don't have any applications. You can add a new one from <Link to='/applications'>/applications</Link> menu</p>
);
}
} }
return apps; return apps;

View File

@ -177,6 +177,7 @@ const BookmarkForm = (props: ComponentProps): JSX.Element => {
value={formData.url} value={formData.url}
onChange={(e) => inputChangeHandler(e)} onChange={(e) => inputChangeHandler(e)}
/> />
<span>Only urls without http[s]:// are supported</span>
</InputGroup> </InputGroup>
<InputGroup> <InputGroup>
<label htmlFor='categoryId'>Bookmark Category</label> <label htmlFor='categoryId'>Bookmark Category</label>

View File

@ -8,6 +8,7 @@ import BookmarkCard from '../BookmarkCard/BookmarkCard';
interface ComponentProps { interface ComponentProps {
categories: Category[]; categories: Category[];
totalCategories?: number;
} }
const BookmarkGrid = (props: ComponentProps): JSX.Element => { const BookmarkGrid = (props: ComponentProps): JSX.Element => {
@ -20,9 +21,15 @@ const BookmarkGrid = (props: ComponentProps): JSX.Element => {
</div> </div>
); );
} else { } else {
bookmarks = ( if (props.totalCategories) {
<p className={classes.BookmarksMessage}>You don't have any bookmarks. You can add a new one from <Link to='/bookmarks'>/bookmarks</Link> menu</p> bookmarks = (
); <p className={classes.BookmarksMessage}>There are no pinned categories. You can pin them from the <Link to='/bookmarks'>/bookmarks</Link> menu</p>
);
} else {
bookmarks = (
<p className={classes.BookmarksMessage}>You don't have any bookmarks. You can add a new one from <Link to='/bookmarks'>/bookmarks</Link> menu</p>
);
}
} }
return bookmarks; return bookmarks;

View File

@ -81,7 +81,10 @@ const Home = (props: ComponentProps): JSX.Element => {
<SectionHeadline title='Applications' link='/applications' /> <SectionHeadline title='Applications' link='/applications' />
{props.appsLoading {props.appsLoading
? <Spinner /> ? <Spinner />
: <AppGrid apps={props.apps.filter((app: App) => app.isPinned)} /> : <AppGrid
apps={props.apps.filter((app: App) => app.isPinned)}
totalApps={props.apps.length}
/>
} }
<div className={classes.HomeSpace}></div> <div className={classes.HomeSpace}></div>
@ -89,7 +92,10 @@ const Home = (props: ComponentProps): JSX.Element => {
<SectionHeadline title='Bookmarks' link='/bookmarks' /> <SectionHeadline title='Bookmarks' link='/bookmarks' />
{props.categoriesLoading {props.categoriesLoading
? <Spinner /> ? <Spinner />
: <BookmarkGrid categories={props.categories.filter((category: Category) => category.isPinned)} /> : <BookmarkGrid
categories={props.categories.filter((category: Category) => category.isPinned)}
totalCategories={props.categories.length}
/>
} }
<Link to='/settings' className={classes.SettingsButton}> <Link to='/settings' className={classes.SettingsButton}>

View File

@ -0,0 +1,88 @@
import { useState, useEffect, ChangeEvent, FormEvent } from 'react';
import axios from 'axios';
import { connect } from 'react-redux';
import InputGroup from '../../UI/Forms/InputGroup/InputGroup';
import Button from '../../UI/Buttons/Button/Button';
import { createNotification } from '../../../store/actions';
import { ApiResponse, Config, NewNotification } from '../../../interfaces';
interface FormState {
customTitle: string;
}
interface ComponentProps {
createNotification: (notification: NewNotification) => void;
}
const OtherSettings = (props: ComponentProps): JSX.Element => {
const [formData, setFormData] = useState<FormState>({
customTitle: document.title
})
// get initial config
useEffect(() => {
axios.get<ApiResponse<Config[]>>('/api/config?keys=customTitle')
.then(data => {
let tmpFormData = { ...formData };
data.data.data.forEach((config: Config) => {
let value: string | number = config.value;
if (config.valueType === 'number') {
value = parseFloat(value);
}
tmpFormData = {
...tmpFormData,
[config.key]: value
}
})
setFormData(tmpFormData);
})
.catch(err => console.log(err));
}, [])
const formSubmitHandler = (e: FormEvent) => {
e.preventDefault();
axios.put<ApiResponse<{}>>('/api/config', formData)
.then(() => {
props.createNotification({
title: 'Success',
message: 'Settings updated'
})
})
.catch((err) => console.log(err));
// update local page title
localStorage.setItem('customTitle', formData.customTitle);
document.title = formData.customTitle;
}
const inputChangeHandler = (e: ChangeEvent<HTMLInputElement>) => {
setFormData({
...formData,
[e.target.name]: e.target.value
})
}
return (
<form onSubmit={(e) => formSubmitHandler(e)}>
<InputGroup>
<label htmlFor='customTitle'>Custom Page Title</label>
<input
type='text'
id='customTitle'
name='customTitle'
placeholder='Flame'
value={formData.customTitle}
onChange={(e) => inputChangeHandler(e)}
/>
</InputGroup>
<Button>Save changes</Button>
</form>
)
}
export default connect(null, { createNotification })(OtherSettings);

View File

@ -6,6 +6,7 @@ import { Container } from '../UI/Layout/Layout';
import Headline from '../UI/Headlines/Headline/Headline'; import Headline from '../UI/Headlines/Headline/Headline';
import Themer from '../Themer/Themer'; import Themer from '../Themer/Themer';
import WeatherSettings from './WeatherSettings/WeatherSettings'; import WeatherSettings from './WeatherSettings/WeatherSettings';
import OtherSettings from './OtherSettings/OtherSettings';
const Settings = (): JSX.Element => { const Settings = (): JSX.Element => {
return ( return (
@ -30,11 +31,19 @@ const Settings = (): JSX.Element => {
to='/settings/weather'> to='/settings/weather'>
Weather Weather
</NavLink> </NavLink>
<NavLink
className={classes.SettingsNavLink}
activeClassName={classes.SettingsNavLinkActive}
exact
to='/settings/other'>
Other
</NavLink>
</nav> </nav>
<section className={classes.SettingsContent}> <section className={classes.SettingsContent}>
<Switch> <Switch>
<Route exact path='/settings' component={Themer} /> <Route exact path='/settings' component={Themer} />
<Route path='/settings/weather' component={WeatherSettings} /> <Route path='/settings/weather' component={WeatherSettings} />
<Route path='/settings/other' component={OtherSettings} />
</Switch> </Switch>
</section> </section>
</div> </div>

View File

@ -3,8 +3,8 @@ const Config = require('../models/Config');
const initConfig = async () => { const initConfig = async () => {
// Config keys // Config keys
const keys = ['WEATHER_API_KEY', 'lat', 'long', 'isCelsius']; const keys = ['WEATHER_API_KEY', 'lat', 'long', 'isCelsius', 'customTitle'];
const values = ['', 0, 0, true]; const values = ['', 0, 0, true, 'Flame'];
// Get config values // Get config values
const configPairs = await Config.findAll({ const configPairs = await Config.findAll({