improving ui / ux

This commit is contained in:
weakmap@gmail.com
2022-03-27 19:42:58 +02:00
parent 5347d0014d
commit 52dafcef97
12 changed files with 150 additions and 169 deletions

View File

@@ -1,3 +1,10 @@
Newer release changelog see https://github.com/orangecoding/fredy/releases
------------
###### [V5.4.6]
- Adding Instana node.js monitoring
-
###### [V5.4.5] ###### [V5.4.5]
- Adding Instana node.js monitoring - Adding Instana node.js monitoring

View File

@@ -1,6 +1,6 @@
{ {
"name": "fredy", "name": "fredy",
"version": "5.4.8", "version": "5.5.0",
"description": "[F]ind [R]eal [E]states [d]amn eas[y].", "description": "[F]ind [R]eal [E]states [d]amn eas[y].",
"scripts": { "scripts": {
"start": "node index.js", "start": "node index.js",
@@ -96,7 +96,7 @@
"css-loader": "6.7.1", "css-loader": "6.7.1",
"eslint": "7.32.0", "eslint": "7.32.0",
"eslint-config-prettier": "8.5.0", "eslint-config-prettier": "8.5.0",
"eslint-plugin-react": "7.29.3", "eslint-plugin-react": "7.29.4",
"file-loader": "6.2.0", "file-loader": "6.2.0",
"history": "5.3.0", "history": "5.3.0",
"husky": "4.3.8", "husky": "4.3.8",

View File

@@ -11,3 +11,11 @@
color: #f1f1f1; color: #f1f1f1;
} }
} }
.ui.inverted.segment{
background: #31303078!important;
}
.ui.black.label, .ui.black.labels .label {
background-color: #31303078!important;
}

View File

@@ -4,7 +4,7 @@
&__active { &__active {
border-bottom: 1px solid #06dcfff2 !important; border-bottom: 1px solid #06dcfff2 !important;
font-weight: 550 !important; font-weight: 550 !important;
color: #78e5ff !important; color: #3ed7ff !important;
margin: 0 0 -1px !important; margin: 0 0 -1px !important;
} }

View File

@@ -0,0 +1,27 @@
import React from 'react';
import { Header, Icon, Popup, Segment } from 'semantic-ui-react';
import './SegmentParts.less';
export const SegmentPart = ({ name, icon = null, children, helpText }) => (
<Segment inverted>
<Header as="h5" inverted sub>
{icon && <Icon name={icon} inverted size="mini" />}
<Header.Content>{name}</Header.Content>
</Header>
<Popup
content={helpText}
trigger={
<span className="generalSettings__help">
{' '}
<Icon name="help circle" inverted />
What is this?
</span>
}
/>
<Segment inverted className="segmentParts">
{children}
</Segment>
</Segment>
);

View File

@@ -0,0 +1,4 @@
.segmentParts {
border: 1px solid #323232 !important;
border-radius: 5px !important;
}

View File

@@ -2,36 +2,13 @@ import React from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { Button, Form, Header, Icon, Message, Popup, Segment } from 'semantic-ui-react'; import { Button, Form, Icon, Message, Segment } from 'semantic-ui-react';
import ToastContext from '../../components/toasts/ToastContext'; import ToastContext from '../../components/toasts/ToastContext';
import Headline from '../../components/headline/Headline'; import Headline from '../../components/headline/Headline';
import { xhrPost } from '../../services/xhr'; import { xhrPost } from '../../services/xhr';
import { SegmentPart } from '../../components/segment/SegmentPart';
import './GeneralSettings.less'; import './GeneralSettings.less';
const SegmentPart = ({ name, icon, children, helpText }) => (
<React.Fragment>
<Header as="h5" inverted attached="top" sub>
<Icon name={icon} inverted size="mini" />
<Header.Content>{name}</Header.Content>
</Header>
<Popup
content={helpText}
trigger={
<span className="generalSettings__help">
{' '}
<Icon name="help circle" inverted />
What is this?
</span>
}
/>
<Segment inverted attached>
{children}
</Segment>
</React.Fragment>
);
const GeneralSettings = function Users() { const GeneralSettings = function Users() {
const dispatch = useDispatch(); const dispatch = useDispatch();
const [loading, setLoading] = React.useState(true); const [loading, setLoading] = React.useState(true);
@@ -111,7 +88,7 @@ const GeneralSettings = function Users() {
{!loading && ( {!loading && (
<React.Fragment> <React.Fragment>
<Headline text="General Settings" /> <Headline text="General Settings" />
<Message info> <Message className="generalSettings__message">
<h5> <h5>
<Icon name="info circle" /> <Icon name="info circle" />
Info Info

View File

@@ -14,4 +14,8 @@
margin-left: 1rem; margin-left: 1rem;
} }
&__message{
background: #60c5df!important;
}
} }

View File

@@ -37,9 +37,13 @@ export default function ProcessingTimes({ processingTimes }) {
{processingTimes.scrapingAntData.plan_total_credits} (250 credits per call) {processingTimes.scrapingAntData.plan_total_credits} (250 credits per call)
</Message.Item> </Message.Item>
</Message.List> </Message.List>
If you want to scrape Immoscout more often, you have to purchase a premium account of ScrapingAnt. You can use If you want to scrape Immoscout more often, you have to purchase a premium account of{' '}
the code <b>FREDY10</b> to get 10% off. (No affiliation, we are <b>not</b> getting paid to recommend <a href="https://scrapingant.com/" target="_blank" rel="noreferrer">
ScrapingAnt. {' '}
ScrapingAnt
</a>
. You can use the code <b>FREDY10</b> to get 10% off. (No affiliation, we are <b>not</b> getting paid to
recommend ScrapingAnt.
</Segment> </Segment>
)} )}
</React.Fragment> </React.Fragment>

View File

@@ -2,7 +2,7 @@ import React, { Fragment, useState } from 'react';
import NotificationAdapterMutator from './components/notificationAdapter/NotificationAdapterMutator'; import NotificationAdapterMutator from './components/notificationAdapter/NotificationAdapterMutator';
import NotificationAdapterTable from '../../../components/table/NotificationAdapterTable'; import NotificationAdapterTable from '../../../components/table/NotificationAdapterTable';
import { Header, Icon, Form, Popup, Button, Label } from 'semantic-ui-react'; import { Icon, Form, Button, Label } from 'semantic-ui-react';
import ProviderTable from '../../../components/table/ProviderTable'; import ProviderTable from '../../../components/table/ProviderTable';
import ProviderMutator from './components/provider/ProviderMutator'; import ProviderMutator from './components/provider/ProviderMutator';
import ToastContext from '../../../components/toasts/ToastContext'; import ToastContext from '../../../components/toasts/ToastContext';
@@ -14,6 +14,7 @@ import { useParams } from 'react-router';
import './JobMutation.less'; import './JobMutation.less';
import Switch from 'react-switch'; import Switch from 'react-switch';
import { SegmentPart } from '../../../components/segment/SegmentPart';
export default function JobMutator() { export default function JobMutator() {
const jobs = useSelector((state) => state.jobs.jobs); const jobs = useSelector((state) => state.jobs.jobs);
@@ -39,27 +40,6 @@ export default function JobMutator() {
const dispatch = useDispatch(); const dispatch = useDispatch();
const ctx = React.useContext(ToastContext); const ctx = React.useContext(ToastContext);
const header = (name, icon) => (
<Header as="h5" inverted>
<Icon name={icon} inverted />
{name}
</Header>
);
const help = (helpText) => (
<div>
<Popup
content={helpText}
trigger={
<Header as="h6" inverted>
<Icon name="help circle" inverted />
What is this?
</Header>
}
/>
</div>
);
const isSavingEnabled = () => { const isSavingEnabled = () => {
return notificationAdapterData.length > 0 && providerData.length > 0 && name != null && name.length > 0; return notificationAdapterData.length > 0 && providerData.length > 0 && name != null && name.length > 0;
}; };
@@ -128,8 +108,8 @@ export default function JobMutator() {
)} )}
<Headline text={jobToBeEdit ? 'Edit a Job' : 'Create a new Job'} /> <Headline text={jobToBeEdit ? 'Edit a Job' : 'Create a new Job'} />
<Form className="jobMutation__form"> <Form>
<div className="jobMutation__block"> <SegmentPart name="Name">
<Form.Input <Form.Input
type="text" type="text"
maxLength={40} maxLength={40}
@@ -140,48 +120,43 @@ export default function JobMutator() {
defaultValue={name} defaultValue={name}
onChange={(e) => setName(e.target.value)} onChange={(e) => setName(e.target.value)}
/> />
</div> </SegmentPart>
<div className="jobMutation__block jobMutation__separator"> <SegmentPart
{header('Provider', 'briefcase')} name="Provider"
icon="briefcase"
helpText={
'A provider is essentially the service (Immowelt etc.) that Fredy is using to search for new listings. When adding a new provider, Fredy will open a new tab pointing ' +
'to the website of this provider. You have to adjust your search parameter and click on "Search". If the results are being shown, copy the browser url. This is the url, Fredy will use ' +
'to search for new listings.'
}
>
<Form.Button primary className="jobMutation__newButton" onClick={() => setProviderCreationVisibility(true)}>
<Icon name="plus" />
Add new Provider
</Form.Button>
<div className="jobMutation__helpContainer">
{help(
'A provider is essentially the service (Immowelt etc.) that Fredy is using to search for new listings. When adding a new provider, Fredy will open a new tab pointing ' +
'to the website of this provider. You have to adjust your search parameter and click on "Search". If the results are being shown, copy the browser url. This is the url, Fredy will use ' +
'to search for new listings.'
)}
<Form.Button primary className="jobMutation__newButton" onClick={() => setProviderCreationVisibility(true)}>
<Icon name="plus" />
Add new Provider
</Form.Button>
</div>
<ProviderTable <ProviderTable
providerData={providerData} providerData={providerData}
onRemove={(providerId) => { onRemove={(providerId) => {
setProviderData(providerData.filter((provider) => provider.id !== providerId)); setProviderData(providerData.filter((provider) => provider.id !== providerId));
}} }}
/> />
</div> </SegmentPart>
<div className="jobMutation__block jobMutation__separator"> <SegmentPart
{header('Notification Adapter', 'bell')} icon="bell"
name="Notification Adapter"
<div className="jobMutation__helpContainer"> helpText="Fredy supports multiple ways to notify you about new findings. These are called notification adapter. You can chose between email, Telegram etc."
{help( >
'Fredy supports multiple ways to notify you about new findings. These are called notification adapter. You can chose between email, Telegram etc.' <Form.Button
)} primary
className="jobMutation__newButton"
<Form.Button onClick={() => setNotificationCreationVisibility(true)}
primary >
className="jobMutation__newButton" <Icon name="plus" />
onClick={() => setNotificationCreationVisibility(true)} Add new Notification Adapter
> </Form.Button>
<Icon name="plus" />
Add new Notification Adapter
</Form.Button>
</div>
<NotificationAdapterTable <NotificationAdapterTable
notificationAdapter={notificationAdapterData} notificationAdapter={notificationAdapterData}
@@ -194,20 +169,15 @@ export default function JobMutator() {
setNotificationCreationVisibility(true); setNotificationCreationVisibility(true);
}} }}
/> />
</div> </SegmentPart>
<div className="jobMutation__block jobMutation__separator">
{header('Blacklist', 'bell')}
<div className="jobMutation__helpContainer">
{help(
'If a listing contains one of these words, it will be filtered out. Words must be comma separated. To remove a word from the black list, just click the red label(s).'
)}
</div>
<SegmentPart
icon="bell"
name="Blacklist"
helpText="If a listing contains one of these words, it will be filtered out. Words must be comma separated. To remove a word from the black list, just click the red label(s)."
>
<Form.Input <Form.Input
type="text" type="text"
className="jobMutation__spaceTop"
maxLength={40} maxLength={40}
placeholder="Comma separated list of blacklisted words" placeholder="Comma separated list of blacklisted words"
autoFocus autoFocus
@@ -232,19 +202,15 @@ export default function JobMutator() {
color="red" color="red"
/> />
))} ))}
</div> </SegmentPart>
<div className="jobMutation__block jobMutation__separator">
{header('Job activation', 'play circle outline')}
<div className="jobMutation__helpContainer">
{help(
'Whether or not the job is activated. If it is not activated, it will be ignored when Fredy checks for new listings.'
)}
</div>
<SegmentPart
icon="play circle outline"
name="Job activation"
helpText="Whether or not the job is activated. If it is not activated, it will be ignored when Fredy checks for new listings."
>
<Switch className="jobMutation__spaceTop" onChange={(checked) => setEnabled(checked)} checked={enabled} /> <Switch className="jobMutation__spaceTop" onChange={(checked) => setEnabled(checked)} checked={enabled} />
</div> </SegmentPart>
<Button color="red" onClick={() => history.push('/jobs')}> <Button color="red" onClick={() => history.push('/jobs')}>
Cancel Cancel

View File

@@ -1,29 +1,5 @@
.jobMutation { .jobMutation {
&__form {
margin-top:2rem;
}
&__block {
margin-bottom: 2rem;
}
&__newButton{ &__newButton{
float: right; float: right;
} }
&__helpContainer {
display: flex;
justify-content: space-between;
align-items: flex-end;
}
&__spaceTop{
margin-top:1rem !important;
}
&__separator{
background-color: #2b2b2b;
border-radius: 10px;
padding: .8rem;
}
} }

View File

@@ -8,6 +8,7 @@ import { useDispatch } from 'react-redux';
import Switch from 'react-switch'; import Switch from 'react-switch';
import './UserMutator.less'; import './UserMutator.less';
import { SegmentPart } from '../../../components/segment/SegmentPart';
const UserMutator = function UserMutator() { const UserMutator = function UserMutator() {
const params = useParams(); const params = useParams();
@@ -69,40 +70,47 @@ const UserMutator = function UserMutator() {
return ( return (
<Form inverted className="userMutator"> <Form inverted className="userMutator">
<Form.Input <SegmentPart name="Username" helpText="The username used to login to Fredy">
type="text" <Form.Input
label="Username" type="text"
maxLength={30} label="Username"
placeholder="Username" maxLength={30}
autoFocus placeholder="Username"
inverted autoFocus
width={6} inverted
defaultValue={username} width={6}
onChange={(e) => setUsername(e.target.value)} defaultValue={username}
/> onChange={(e) => setUsername(e.target.value)}
<Form.Input />
type="password" </SegmentPart>
label="Password" <SegmentPart name="Password" helpText="The password used to login to Fredy">
placeholder="Password" <Form.Input
inverted type="password"
width={6} label="Password"
defaultValue={password} placeholder="Password"
onChange={(e) => setPassword(e.target.value)} inverted
/> width={6}
<Form.Input defaultValue={password}
type="password" onChange={(e) => setPassword(e.target.value)}
label="Retype password" />
placeholder="Retype password" </SegmentPart>
inverted <SegmentPart name="Retype password" helpText="Retype the password to make sure they match">
width={6} <Form.Input
defaultValue={password2} type="password"
onChange={(e) => setPassword2(e.target.value)} label="Retype password"
/> placeholder="Retype password"
<Form.Field> inverted
<label>Is user an admin?</label> width={6}
<Switch checked={isAdmin} onChange={(checked) => setIsAdmin(checked)} /> defaultValue={password2}
</Form.Field> onChange={(e) => setPassword2(e.target.value)}
/>
</SegmentPart>
<SegmentPart name="Admin use" helpText="Check this if the user is an administrator">
<Form.Field>
<label>Is user an admin?</label>
<Switch checked={isAdmin} onChange={(checked) => setIsAdmin(checked)} />
</Form.Field>
</SegmentPart>
<Button color="red" onClick={() => history.push('/users')}> <Button color="red" onClick={() => history.push('/users')}>
Cancel Cancel
</Button> </Button>