mirror of
https://github.com/orangecoding/fredy.git
synced 2026-06-16 12:31:07 +00:00
improving ui / ux
This commit is contained in:
@@ -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]
|
||||
- Adding Instana node.js monitoring
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "fredy",
|
||||
"version": "5.4.8",
|
||||
"version": "5.5.0",
|
||||
"description": "[F]ind [R]eal [E]states [d]amn eas[y].",
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
@@ -96,7 +96,7 @@
|
||||
"css-loader": "6.7.1",
|
||||
"eslint": "7.32.0",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"eslint-plugin-react": "7.29.3",
|
||||
"eslint-plugin-react": "7.29.4",
|
||||
"file-loader": "6.2.0",
|
||||
"history": "5.3.0",
|
||||
"husky": "4.3.8",
|
||||
|
||||
@@ -10,4 +10,12 @@
|
||||
background-color: #3f3e3ef5;
|
||||
color: #f1f1f1;
|
||||
}
|
||||
}
|
||||
|
||||
.ui.inverted.segment{
|
||||
background: #31303078!important;
|
||||
}
|
||||
|
||||
.ui.black.label, .ui.black.labels .label {
|
||||
background-color: #31303078!important;
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
&__active {
|
||||
border-bottom: 1px solid #06dcfff2 !important;
|
||||
font-weight: 550 !important;
|
||||
color: #78e5ff !important;
|
||||
color: #3ed7ff !important;
|
||||
margin: 0 0 -1px !important;
|
||||
}
|
||||
|
||||
|
||||
27
ui/src/components/segment/SegmentPart.js
Normal file
27
ui/src/components/segment/SegmentPart.js
Normal 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>
|
||||
);
|
||||
4
ui/src/components/segment/SegmentParts.less
Normal file
4
ui/src/components/segment/SegmentParts.less
Normal file
@@ -0,0 +1,4 @@
|
||||
.segmentParts {
|
||||
border: 1px solid #323232 !important;
|
||||
border-radius: 5px !important;
|
||||
}
|
||||
@@ -2,36 +2,13 @@ import React from 'react';
|
||||
|
||||
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 Headline from '../../components/headline/Headline';
|
||||
import { xhrPost } from '../../services/xhr';
|
||||
|
||||
import { SegmentPart } from '../../components/segment/SegmentPart';
|
||||
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 dispatch = useDispatch();
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
@@ -111,7 +88,7 @@ const GeneralSettings = function Users() {
|
||||
{!loading && (
|
||||
<React.Fragment>
|
||||
<Headline text="General Settings" />
|
||||
<Message info>
|
||||
<Message className="generalSettings__message">
|
||||
<h5>
|
||||
<Icon name="info circle" />
|
||||
Info
|
||||
|
||||
@@ -14,4 +14,8 @@
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
&__message{
|
||||
background: #60c5df!important;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -37,9 +37,13 @@ export default function ProcessingTimes({ processingTimes }) {
|
||||
{processingTimes.scrapingAntData.plan_total_credits} (250 credits per call)
|
||||
</Message.Item>
|
||||
</Message.List>
|
||||
If you want to scrape Immoscout more often, you have to purchase a premium account of ScrapingAnt. You can use
|
||||
the code <b>FREDY10</b> to get 10% off. (No affiliation, we are <b>not</b> getting paid to recommend
|
||||
ScrapingAnt.
|
||||
If you want to scrape Immoscout more often, you have to purchase a premium account of{' '}
|
||||
<a href="https://scrapingant.com/" target="_blank" rel="noreferrer">
|
||||
{' '}
|
||||
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>
|
||||
)}
|
||||
</React.Fragment>
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { Fragment, useState } from 'react';
|
||||
|
||||
import NotificationAdapterMutator from './components/notificationAdapter/NotificationAdapterMutator';
|
||||
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 ProviderMutator from './components/provider/ProviderMutator';
|
||||
import ToastContext from '../../../components/toasts/ToastContext';
|
||||
@@ -14,6 +14,7 @@ import { useParams } from 'react-router';
|
||||
|
||||
import './JobMutation.less';
|
||||
import Switch from 'react-switch';
|
||||
import { SegmentPart } from '../../../components/segment/SegmentPart';
|
||||
|
||||
export default function JobMutator() {
|
||||
const jobs = useSelector((state) => state.jobs.jobs);
|
||||
@@ -39,27 +40,6 @@ export default function JobMutator() {
|
||||
const dispatch = useDispatch();
|
||||
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 = () => {
|
||||
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'} />
|
||||
<Form className="jobMutation__form">
|
||||
<div className="jobMutation__block">
|
||||
<Form>
|
||||
<SegmentPart name="Name">
|
||||
<Form.Input
|
||||
type="text"
|
||||
maxLength={40}
|
||||
@@ -140,48 +120,43 @@ export default function JobMutator() {
|
||||
defaultValue={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</SegmentPart>
|
||||
|
||||
<div className="jobMutation__block jobMutation__separator">
|
||||
{header('Provider', 'briefcase')}
|
||||
<SegmentPart
|
||||
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
|
||||
providerData={providerData}
|
||||
onRemove={(providerId) => {
|
||||
setProviderData(providerData.filter((provider) => provider.id !== providerId));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</SegmentPart>
|
||||
|
||||
<div className="jobMutation__block jobMutation__separator">
|
||||
{header('Notification Adapter', 'bell')}
|
||||
|
||||
<div className="jobMutation__helpContainer">
|
||||
{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"
|
||||
onClick={() => setNotificationCreationVisibility(true)}
|
||||
>
|
||||
<Icon name="plus" />
|
||||
Add new Notification Adapter
|
||||
</Form.Button>
|
||||
</div>
|
||||
<SegmentPart
|
||||
icon="bell"
|
||||
name="Notification Adapter"
|
||||
helpText="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"
|
||||
onClick={() => setNotificationCreationVisibility(true)}
|
||||
>
|
||||
<Icon name="plus" />
|
||||
Add new Notification Adapter
|
||||
</Form.Button>
|
||||
|
||||
<NotificationAdapterTable
|
||||
notificationAdapter={notificationAdapterData}
|
||||
@@ -194,20 +169,15 @@ export default function JobMutator() {
|
||||
setNotificationCreationVisibility(true);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<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
|
||||
type="text"
|
||||
className="jobMutation__spaceTop"
|
||||
maxLength={40}
|
||||
placeholder="Comma separated list of blacklisted words"
|
||||
autoFocus
|
||||
@@ -232,19 +202,15 @@ export default function JobMutator() {
|
||||
color="red"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<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} />
|
||||
</div>
|
||||
</SegmentPart>
|
||||
|
||||
<Button color="red" onClick={() => history.push('/jobs')}>
|
||||
Cancel
|
||||
|
||||
@@ -1,29 +1,5 @@
|
||||
.jobMutation {
|
||||
|
||||
&__form {
|
||||
margin-top:2rem;
|
||||
}
|
||||
|
||||
&__block {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
&__newButton{
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { useDispatch } from 'react-redux';
|
||||
import Switch from 'react-switch';
|
||||
|
||||
import './UserMutator.less';
|
||||
import { SegmentPart } from '../../../components/segment/SegmentPart';
|
||||
|
||||
const UserMutator = function UserMutator() {
|
||||
const params = useParams();
|
||||
@@ -69,40 +70,47 @@ const UserMutator = function UserMutator() {
|
||||
|
||||
return (
|
||||
<Form inverted className="userMutator">
|
||||
<Form.Input
|
||||
type="text"
|
||||
label="Username"
|
||||
maxLength={30}
|
||||
placeholder="Username"
|
||||
autoFocus
|
||||
inverted
|
||||
width={6}
|
||||
defaultValue={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
<Form.Input
|
||||
type="password"
|
||||
label="Password"
|
||||
placeholder="Password"
|
||||
inverted
|
||||
width={6}
|
||||
defaultValue={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
<Form.Input
|
||||
type="password"
|
||||
label="Retype password"
|
||||
placeholder="Retype password"
|
||||
inverted
|
||||
width={6}
|
||||
defaultValue={password2}
|
||||
onChange={(e) => setPassword2(e.target.value)}
|
||||
/>
|
||||
<Form.Field>
|
||||
<label>Is user an admin?</label>
|
||||
<Switch checked={isAdmin} onChange={(checked) => setIsAdmin(checked)} />
|
||||
</Form.Field>
|
||||
|
||||
<SegmentPart name="Username" helpText="The username used to login to Fredy">
|
||||
<Form.Input
|
||||
type="text"
|
||||
label="Username"
|
||||
maxLength={30}
|
||||
placeholder="Username"
|
||||
autoFocus
|
||||
inverted
|
||||
width={6}
|
||||
defaultValue={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
</SegmentPart>
|
||||
<SegmentPart name="Password" helpText="The password used to login to Fredy">
|
||||
<Form.Input
|
||||
type="password"
|
||||
label="Password"
|
||||
placeholder="Password"
|
||||
inverted
|
||||
width={6}
|
||||
defaultValue={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
</SegmentPart>
|
||||
<SegmentPart name="Retype password" helpText="Retype the password to make sure they match">
|
||||
<Form.Input
|
||||
type="password"
|
||||
label="Retype password"
|
||||
placeholder="Retype password"
|
||||
inverted
|
||||
width={6}
|
||||
defaultValue={password2}
|
||||
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')}>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user