mirror of
https://github.com/orangecoding/fredy.git
synced 2026-06-16 12:31:07 +00:00
Mobile view and wording (#151)
* feat(ui): simplified titles and adjusted some wording * style(ui): simplified some views for mobile * style(ui): make job table responsive for mobile * style(ui): login button gap * style(ui): dont hide mobile columns * fix: method return type
This commit is contained in:
@@ -4,6 +4,7 @@ import { Tabs, TabPane } from '@douyinfe/semi-ui';
|
|||||||
|
|
||||||
import { useLocation } from 'react-router';
|
import { useLocation } from 'react-router';
|
||||||
import { IconUser, IconTerminal, IconSetting } from '@douyinfe/semi-icons';
|
import { IconUser, IconTerminal, IconSetting } from '@douyinfe/semi-icons';
|
||||||
|
import './Menu.less';
|
||||||
|
|
||||||
function parsePathName(name) {
|
function parsePathName(name) {
|
||||||
const split = name.split('/').filter((s) => s.length !== 0);
|
const split = name.split('/').filter((s) => s.length !== 0);
|
||||||
@@ -14,7 +15,12 @@ const TopMenu = function TopMenu({ isAdmin }) {
|
|||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
return (
|
return (
|
||||||
<Tabs type="line" activeKey={parsePathName(location.pathname)} onTabClick={(key) => history.push(key)}>
|
<Tabs
|
||||||
|
className="menu"
|
||||||
|
type="line"
|
||||||
|
activeKey={parsePathName(location.pathname)}
|
||||||
|
onTabClick={(key) => history.push(key)}
|
||||||
|
>
|
||||||
<TabPane
|
<TabPane
|
||||||
itemKey="/jobs"
|
itemKey="/jobs"
|
||||||
tab={
|
tab={
|
||||||
|
|||||||
3
ui/src/components/menu/Menu.less
Normal file
3
ui/src/components/menu/Menu.less
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.menu {
|
||||||
|
margin-top: 3rem;
|
||||||
|
}
|
||||||
@@ -3,11 +3,14 @@ import React from 'react';
|
|||||||
import { Button, Empty, Table, Switch } from '@douyinfe/semi-ui';
|
import { Button, Empty, Table, Switch } from '@douyinfe/semi-ui';
|
||||||
import { IconDelete, IconEdit, IconHistogram } from '@douyinfe/semi-icons';
|
import { IconDelete, IconEdit, IconHistogram } from '@douyinfe/semi-icons';
|
||||||
import { IllustrationNoResult, IllustrationNoResultDark } from '@douyinfe/semi-illustrations';
|
import { IllustrationNoResult, IllustrationNoResultDark } from '@douyinfe/semi-illustrations';
|
||||||
|
|
||||||
|
import './JobTable.less';
|
||||||
|
|
||||||
const empty = (
|
const empty = (
|
||||||
<Empty
|
<Empty
|
||||||
image={<IllustrationNoResult />}
|
image={<IllustrationNoResult />}
|
||||||
darkModeImage={<IllustrationNoResultDark />}
|
darkModeImage={<IllustrationNoResultDark />}
|
||||||
description={'No jobs available'}
|
description={'No jobs available.'}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -25,25 +28,25 @@ export default function JobTable({ jobs = {}, onJobRemoval, onJobStatusChanged,
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Job Name',
|
title: 'Name',
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Number of findings',
|
title: 'Findings',
|
||||||
dataIndex: 'numberOfFoundListings',
|
dataIndex: 'numberOfFoundListings',
|
||||||
render: (value) => {
|
render: (value) => {
|
||||||
return value || 0;
|
return value || 0;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Active provider',
|
title: 'Providers',
|
||||||
dataIndex: 'provider',
|
dataIndex: 'provider',
|
||||||
render: (value) => {
|
render: (value) => {
|
||||||
return value.length || 0;
|
return value.length || 0;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Active notification adapter',
|
title: 'Notification adapters',
|
||||||
dataIndex: 'notificationAdapter',
|
dataIndex: 'notificationAdapter',
|
||||||
render: (value) => {
|
render: (value) => {
|
||||||
return value.length || 0;
|
return value.length || 0;
|
||||||
@@ -54,19 +57,9 @@ export default function JobTable({ jobs = {}, onJobRemoval, onJobStatusChanged,
|
|||||||
dataIndex: 'tools',
|
dataIndex: 'tools',
|
||||||
render: (_, job) => {
|
render: (_, job) => {
|
||||||
return (
|
return (
|
||||||
<div style={{ float: 'right' }}>
|
<div className="interactions">
|
||||||
<Button
|
<Button type="primary" icon={<IconHistogram />} onClick={() => onJobInsight(job.id)} />
|
||||||
type="primary"
|
<Button type="secondary" icon={<IconEdit />} onClick={() => onJobEdit(job.id)} />
|
||||||
icon={<IconHistogram />}
|
|
||||||
onClick={() => onJobInsight(job.id)}
|
|
||||||
style={{ marginRight: '1rem' }}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
type="secondary"
|
|
||||||
icon={<IconEdit />}
|
|
||||||
onClick={() => onJobEdit(job.id)}
|
|
||||||
style={{ marginRight: '1rem' }}
|
|
||||||
/>
|
|
||||||
<Button type="danger" icon={<IconDelete />} onClick={() => onJobRemoval(job.id)} />
|
<Button type="danger" icon={<IconDelete />} onClick={() => onJobRemoval(job.id)} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
12
ui/src/components/table/JobTable.less
Normal file
12
ui/src/components/table/JobTable.less
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
.interactions {
|
||||||
|
float: right;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.interactions {
|
||||||
|
flex-direction: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,10 +7,10 @@ export default function NotificationAdapterTable({ notificationAdapter = [], onR
|
|||||||
return (
|
return (
|
||||||
<Table
|
<Table
|
||||||
pagination={false}
|
pagination={false}
|
||||||
empty={<Empty description="No Data" />}
|
empty={<Empty description="No notification adapters found." />}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
title: 'Notification Adapter Name',
|
title: 'Name',
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ export default function ProviderTable({ providerData = [], onRemove } = {}) {
|
|||||||
return (
|
return (
|
||||||
<Table
|
<Table
|
||||||
pagination={false}
|
pagination={false}
|
||||||
empty={<Empty description="No Provider available" />}
|
empty={<Empty description="No providers found." />}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
title: 'Provider Name',
|
title: 'Name',
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Provider Url',
|
title: 'URL',
|
||||||
dataIndex: 'url',
|
dataIndex: 'url',
|
||||||
render: (_, data) => {
|
render: (_, data) => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const empty = (
|
|||||||
<Empty
|
<Empty
|
||||||
image={<IllustrationNoResult />}
|
image={<IllustrationNoResult />}
|
||||||
darkModeImage={<IllustrationNoResultDark />}
|
darkModeImage={<IllustrationNoResultDark />}
|
||||||
description={'No user available'}
|
description={'No users found.'}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -71,28 +71,20 @@ const GeneralSettings = function GeneralSettings() {
|
|||||||
|
|
||||||
const nullOrEmpty = (val) => val == null || val.length === 0;
|
const nullOrEmpty = (val) => val == null || val.length === 0;
|
||||||
|
|
||||||
const throwMessage = (message, type) => {
|
|
||||||
if (type === 'error') {
|
|
||||||
Toast.error(message);
|
|
||||||
} else {
|
|
||||||
Toast.success(message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onStore = async () => {
|
const onStore = async () => {
|
||||||
if (nullOrEmpty(interval)) {
|
if (nullOrEmpty(interval)) {
|
||||||
throwMessage('Interval may not be empty.', 'error');
|
Toast.error('Interval may not be empty.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (nullOrEmpty(port)) {
|
if (nullOrEmpty(port)) {
|
||||||
throwMessage('Port may not be empty.', 'error');
|
Toast.error('Port may not be empty.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
(!nullOrEmpty(workingHourFrom) && nullOrEmpty(workingHourTo)) ||
|
(!nullOrEmpty(workingHourFrom) && nullOrEmpty(workingHourTo)) ||
|
||||||
(nullOrEmpty(workingHourFrom) && !nullOrEmpty(workingHourTo))
|
(nullOrEmpty(workingHourFrom) && !nullOrEmpty(workingHourTo))
|
||||||
) {
|
) {
|
||||||
throwMessage('Working hours to and from must be set if either to or from has been set before.', 'error');
|
Toast.error('Working hours to and from must be set if either to or from has been set before.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -109,13 +101,13 @@ const GeneralSettings = function GeneralSettings() {
|
|||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
console.error(exception);
|
console.error(exception);
|
||||||
if (exception?.json?.message != null) {
|
if (exception?.json?.message != null) {
|
||||||
throwMessage(exception.json.message, 'error');
|
Toast.error(exception.json.message);
|
||||||
} else {
|
} else {
|
||||||
throwMessage('Error while trying to store settings.', 'error');
|
Toast.error('Error while trying to store settings.');
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throwMessage('Settings stored successfully. We will reload your browser in 3 seconds.', 'success');
|
Toast.success('Settings stored successfully. We will reload your browser in 3 seconds.');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
location.reload();
|
location.reload();
|
||||||
}, 3000);
|
}, 3000);
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export default function JobMutator() {
|
|||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const isSavingEnabled = () => {
|
const isSavingEnabled = () => {
|
||||||
return notificationAdapterData.length > 0 && providerData.length > 0 && name != null && name.length > 0;
|
return Boolean(notificationAdapterData.length && providerData.length && name);
|
||||||
};
|
};
|
||||||
|
|
||||||
const mutateJob = async () => {
|
const mutateJob = async () => {
|
||||||
@@ -105,13 +105,13 @@ export default function JobMutator() {
|
|||||||
</SegmentPart>
|
</SegmentPart>
|
||||||
<Divider margin="1rem" />
|
<Divider margin="1rem" />
|
||||||
<SegmentPart
|
<SegmentPart
|
||||||
name="Provider"
|
name="Providers"
|
||||||
icon="briefcase"
|
icon="briefcase"
|
||||||
helpText={
|
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 ' +
|
A provider is essentially the service (e.g. ImmoScout24, Kleinanzeigen) that Fredy searches for new listings.
|
||||||
'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 ' +
|
Fredy will open a new tab pointing to the website of this provider. You have to adjust your search parameter
|
||||||
'to search for new listings.'
|
and click on "Search". If the results are being shown, copy the browser URL in here.
|
||||||
}
|
`}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -132,7 +132,7 @@ export default function JobMutator() {
|
|||||||
<Divider margin="1rem" />
|
<Divider margin="1rem" />
|
||||||
<SegmentPart
|
<SegmentPart
|
||||||
icon="bell"
|
icon="bell"
|
||||||
name="Notification Adapter"
|
name="Notification Adapters"
|
||||||
helpText="Fredy supports multiple ways to notify you about new findings. These are called notification adapter. You can chose between email, Telegram etc."
|
helpText="Fredy supports multiple ways to notify you about new findings. These are called notification adapter. You can chose between email, Telegram etc."
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
@@ -172,7 +172,7 @@ export default function JobMutator() {
|
|||||||
<SegmentPart
|
<SegmentPart
|
||||||
icon="play circle outline"
|
icon="play circle outline"
|
||||||
name="Job activation"
|
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."
|
helpText="Whether or not the job is activated. Inactive jobs 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} />
|
||||||
</SegmentPart>
|
</SegmentPart>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Logo from '../../components/logo/Logo';
|
|||||||
import { xhrPost } from '../../services/xhr';
|
import { xhrPost } from '../../services/xhr';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { Input, Button, Banner } from '@douyinfe/semi-ui';
|
import { Input, Button, Banner, Toast } from '@douyinfe/semi-ui';
|
||||||
|
|
||||||
import './login.less';
|
import './login.less';
|
||||||
import { IconUser, IconLock } from '@douyinfe/semi-icons';
|
import { IconUser, IconLock } from '@douyinfe/semi-icons';
|
||||||
@@ -27,20 +27,24 @@ export default function Login() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const tryLogin = async () => {
|
const tryLogin = async () => {
|
||||||
if (username.length === 0 || password.length === 0) {
|
if (!username?.trim() || !password) {
|
||||||
setError('Username and password are mandatory.');
|
setError('Username and password are mandatory.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
setError(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await xhrPost('/api/login', {
|
await xhrPost('/api/login', {
|
||||||
username,
|
username: username.trim(),
|
||||||
password,
|
password,
|
||||||
});
|
});
|
||||||
setError(null);
|
|
||||||
} catch (Exception) {
|
} catch (Exception) {
|
||||||
setError('Login not successful...');
|
Toast.error('Login unsuccessful…');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Toast.success('Login successful!');
|
||||||
|
|
||||||
await dispatch.user.getCurrentUser();
|
await dispatch.user.getCurrentUser();
|
||||||
history.push('/jobs');
|
history.push('/jobs');
|
||||||
};
|
};
|
||||||
@@ -58,7 +62,6 @@ export default function Login() {
|
|||||||
placeholder="Username"
|
placeholder="Username"
|
||||||
value={username}
|
value={username}
|
||||||
showClear
|
showClear
|
||||||
style={{ marginTop: error ? '1rem' : '4rem' }}
|
|
||||||
autoFocus
|
autoFocus
|
||||||
onChange={(value) => setUserName(value)}
|
onChange={(value) => setUserName(value)}
|
||||||
onKeyPress={async (e) => {
|
onKeyPress={async (e) => {
|
||||||
@@ -74,7 +77,6 @@ export default function Login() {
|
|||||||
prefix={<IconLock />}
|
prefix={<IconLock />}
|
||||||
value={password}
|
value={password}
|
||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
style={{ marginTop: '2rem' }}
|
|
||||||
onChange={(value) => setPassword(value)}
|
onChange={(value) => setPassword(value)}
|
||||||
onKeyPress={async (e) => {
|
onKeyPress={async (e) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
@@ -83,10 +85,10 @@ export default function Login() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button type="primary" onClick={tryLogin} theme="solid" style={{ marginTop: '3rem' }}>
|
<Button type="primary" onClick={tryLogin} theme="solid" style={{ marginTop: '1rem' }}>
|
||||||
Login
|
Login
|
||||||
</Button>
|
</Button>
|
||||||
<br />
|
|
||||||
{demoMode && (
|
{demoMode && (
|
||||||
<Banner
|
<Banner
|
||||||
fullMode={true}
|
fullMode={true}
|
||||||
|
|||||||
@@ -20,13 +20,13 @@
|
|||||||
&__loginWrapper {
|
&__loginWrapper {
|
||||||
border: 1px solid #555050;
|
border: 1px solid #555050;
|
||||||
border-radius: 30px;
|
border-radius: 30px;
|
||||||
height: 23rem;
|
|
||||||
width: 30rem;
|
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
background-color: #151313ab;
|
background-color: #151313ab;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
form {
|
form {
|
||||||
|
|||||||
Reference in New Issue
Block a user