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 { IconUser, IconTerminal, IconSetting } from '@douyinfe/semi-icons';
|
||||
import './Menu.less';
|
||||
|
||||
function parsePathName(name) {
|
||||
const split = name.split('/').filter((s) => s.length !== 0);
|
||||
@@ -14,7 +15,12 @@ const TopMenu = function TopMenu({ isAdmin }) {
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
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
|
||||
itemKey="/jobs"
|
||||
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 { IconDelete, IconEdit, IconHistogram } from '@douyinfe/semi-icons';
|
||||
import { IllustrationNoResult, IllustrationNoResultDark } from '@douyinfe/semi-illustrations';
|
||||
|
||||
import './JobTable.less';
|
||||
|
||||
const empty = (
|
||||
<Empty
|
||||
image={<IllustrationNoResult />}
|
||||
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',
|
||||
},
|
||||
{
|
||||
title: 'Number of findings',
|
||||
title: 'Findings',
|
||||
dataIndex: 'numberOfFoundListings',
|
||||
render: (value) => {
|
||||
return value || 0;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Active provider',
|
||||
title: 'Providers',
|
||||
dataIndex: 'provider',
|
||||
render: (value) => {
|
||||
return value.length || 0;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Active notification adapter',
|
||||
title: 'Notification adapters',
|
||||
dataIndex: 'notificationAdapter',
|
||||
render: (value) => {
|
||||
return value.length || 0;
|
||||
@@ -54,19 +57,9 @@ export default function JobTable({ jobs = {}, onJobRemoval, onJobStatusChanged,
|
||||
dataIndex: 'tools',
|
||||
render: (_, job) => {
|
||||
return (
|
||||
<div style={{ float: 'right' }}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<IconHistogram />}
|
||||
onClick={() => onJobInsight(job.id)}
|
||||
style={{ marginRight: '1rem' }}
|
||||
/>
|
||||
<Button
|
||||
type="secondary"
|
||||
icon={<IconEdit />}
|
||||
onClick={() => onJobEdit(job.id)}
|
||||
style={{ marginRight: '1rem' }}
|
||||
/>
|
||||
<div className="interactions">
|
||||
<Button type="primary" icon={<IconHistogram />} onClick={() => onJobInsight(job.id)} />
|
||||
<Button type="secondary" icon={<IconEdit />} onClick={() => onJobEdit(job.id)} />
|
||||
<Button type="danger" icon={<IconDelete />} onClick={() => onJobRemoval(job.id)} />
|
||||
</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 (
|
||||
<Table
|
||||
pagination={false}
|
||||
empty={<Empty description="No Data" />}
|
||||
empty={<Empty description="No notification adapters found." />}
|
||||
columns={[
|
||||
{
|
||||
title: 'Notification Adapter Name',
|
||||
title: 'Name',
|
||||
dataIndex: 'name',
|
||||
},
|
||||
|
||||
|
||||
@@ -7,14 +7,14 @@ export default function ProviderTable({ providerData = [], onRemove } = {}) {
|
||||
return (
|
||||
<Table
|
||||
pagination={false}
|
||||
empty={<Empty description="No Provider available" />}
|
||||
empty={<Empty description="No providers found." />}
|
||||
columns={[
|
||||
{
|
||||
title: 'Provider Name',
|
||||
title: 'Name',
|
||||
dataIndex: 'name',
|
||||
},
|
||||
{
|
||||
title: 'Provider Url',
|
||||
title: 'URL',
|
||||
dataIndex: 'url',
|
||||
render: (_, data) => {
|
||||
return (
|
||||
|
||||
@@ -9,7 +9,7 @@ const empty = (
|
||||
<Empty
|
||||
image={<IllustrationNoResult />}
|
||||
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 throwMessage = (message, type) => {
|
||||
if (type === 'error') {
|
||||
Toast.error(message);
|
||||
} else {
|
||||
Toast.success(message);
|
||||
}
|
||||
};
|
||||
|
||||
const onStore = async () => {
|
||||
if (nullOrEmpty(interval)) {
|
||||
throwMessage('Interval may not be empty.', 'error');
|
||||
Toast.error('Interval may not be empty.');
|
||||
return;
|
||||
}
|
||||
if (nullOrEmpty(port)) {
|
||||
throwMessage('Port may not be empty.', 'error');
|
||||
Toast.error('Port may not be empty.');
|
||||
return;
|
||||
}
|
||||
if (
|
||||
(!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;
|
||||
}
|
||||
try {
|
||||
@@ -109,13 +101,13 @@ const GeneralSettings = function GeneralSettings() {
|
||||
} catch (exception) {
|
||||
console.error(exception);
|
||||
if (exception?.json?.message != null) {
|
||||
throwMessage(exception.json.message, 'error');
|
||||
Toast.error(exception.json.message);
|
||||
} else {
|
||||
throwMessage('Error while trying to store settings.', 'error');
|
||||
Toast.error('Error while trying to store settings.');
|
||||
}
|
||||
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(() => {
|
||||
location.reload();
|
||||
}, 3000);
|
||||
|
||||
@@ -38,7 +38,7 @@ export default function JobMutator() {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const isSavingEnabled = () => {
|
||||
return notificationAdapterData.length > 0 && providerData.length > 0 && name != null && name.length > 0;
|
||||
return Boolean(notificationAdapterData.length && providerData.length && name);
|
||||
};
|
||||
|
||||
const mutateJob = async () => {
|
||||
@@ -105,13 +105,13 @@ export default function JobMutator() {
|
||||
</SegmentPart>
|
||||
<Divider margin="1rem" />
|
||||
<SegmentPart
|
||||
name="Provider"
|
||||
name="Providers"
|
||||
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.'
|
||||
}
|
||||
helpText={`
|
||||
A provider is essentially the service (e.g. ImmoScout24, Kleinanzeigen) that Fredy searches for new listings.
|
||||
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 in here.
|
||||
`}
|
||||
>
|
||||
<Button
|
||||
type="primary"
|
||||
@@ -132,7 +132,7 @@ export default function JobMutator() {
|
||||
<Divider margin="1rem" />
|
||||
<SegmentPart
|
||||
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."
|
||||
>
|
||||
<Button
|
||||
@@ -172,7 +172,7 @@ export default function JobMutator() {
|
||||
<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."
|
||||
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} />
|
||||
</SegmentPart>
|
||||
|
||||
@@ -5,7 +5,7 @@ import Logo from '../../components/logo/Logo';
|
||||
import { xhrPost } from '../../services/xhr';
|
||||
import { useHistory } from 'react-router';
|
||||
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 { IconUser, IconLock } from '@douyinfe/semi-icons';
|
||||
@@ -27,20 +27,24 @@ export default function Login() {
|
||||
}, []);
|
||||
|
||||
const tryLogin = async () => {
|
||||
if (username.length === 0 || password.length === 0) {
|
||||
if (!username?.trim() || !password) {
|
||||
setError('Username and password are mandatory.');
|
||||
return;
|
||||
}
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
await xhrPost('/api/login', {
|
||||
username,
|
||||
username: username.trim(),
|
||||
password,
|
||||
});
|
||||
setError(null);
|
||||
} catch (Exception) {
|
||||
setError('Login not successful...');
|
||||
Toast.error('Login unsuccessful…');
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.success('Login successful!');
|
||||
|
||||
await dispatch.user.getCurrentUser();
|
||||
history.push('/jobs');
|
||||
};
|
||||
@@ -58,7 +62,6 @@ export default function Login() {
|
||||
placeholder="Username"
|
||||
value={username}
|
||||
showClear
|
||||
style={{ marginTop: error ? '1rem' : '4rem' }}
|
||||
autoFocus
|
||||
onChange={(value) => setUserName(value)}
|
||||
onKeyPress={async (e) => {
|
||||
@@ -74,7 +77,6 @@ export default function Login() {
|
||||
prefix={<IconLock />}
|
||||
value={password}
|
||||
placeholder="Password"
|
||||
style={{ marginTop: '2rem' }}
|
||||
onChange={(value) => setPassword(value)}
|
||||
onKeyPress={async (e) => {
|
||||
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
|
||||
</Button>
|
||||
<br />
|
||||
|
||||
{demoMode && (
|
||||
<Banner
|
||||
fullMode={true}
|
||||
|
||||
@@ -20,13 +20,13 @@
|
||||
&__loginWrapper {
|
||||
border: 1px solid #555050;
|
||||
border-radius: 30px;
|
||||
height: 23rem;
|
||||
width: 30rem;
|
||||
|
||||
z-index: 1;
|
||||
background-color: #151313ab;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 2rem;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
form {
|
||||
|
||||
Reference in New Issue
Block a user