2021-05-30 09:37:45 +02:00
import React from 'react' ;
2025-07-23 08:47:26 +02:00
import { useDispatch , useSelector } from 'react-redux' ;
2021-05-30 09:37:45 +02:00
2025-07-23 08:47:26 +02:00
import { Divider , TimePicker , Button , Checkbox } from '@douyinfe/semi-ui' ;
import { InputNumber } from '@douyinfe/semi-ui' ;
2021-05-30 09:37:45 +02:00
import Headline from '../../components/headline/Headline' ;
2025-07-23 08:47:26 +02:00
import { xhrPost } from '../../services/xhr' ;
import { SegmentPart } from '../../components/segment/SegmentPart' ;
import { Banner , Toast } from '@douyinfe/semi-ui' ;
import {
IconSave ,
IconCalendar ,
IconRefresh ,
IconSignal ,
IconLineChartStroked ,
IconSearch ,
} from '@douyinfe/semi-icons' ;
2021-05-30 09:37:45 +02:00
import './GeneralSettings.less' ;
2023-03-20 08:52:13 +01:00
function formatFromTimestamp ( ts ) {
2025-07-23 08:47:26 +02:00
const date = new Date ( ts ) ;
return ` ${ date . getHours ( ) } : ${ date . getMinutes ( ) > 9 ? date . getMinutes ( ) : '0' + date . getMinutes ( ) } ` ;
2023-03-20 08:52:13 +01:00
}
function formatFromTBackend ( time ) {
2025-07-23 08:47:26 +02:00
if ( time == null || time . length === 0 ) {
return null ;
}
const date = new Date ( ) ;
const split = time . split ( ':' ) ;
date . setHours ( split [ 0 ] ) ;
date . setMinutes ( split [ 1 ] ) ;
return date . getTime ( ) ;
2023-03-20 08:52:13 +01:00
}
const GeneralSettings = function GeneralSettings ( ) {
2025-07-23 08:47:26 +02:00
const dispatch = useDispatch ( ) ;
const [ loading , setLoading ] = React . useState ( true ) ;
const settings = useSelector ( ( state ) => state . generalSettings . settings ) ;
const [ interval , setInterval ] = React . useState ( '' ) ;
const [ port , setPort ] = React . useState ( '' ) ;
const [ workingHourFrom , setWorkingHourFrom ] = React . useState ( null ) ;
const [ workingHourTo , setWorkingHourTo ] = React . useState ( null ) ;
const [ demoMode , setDemoMode ] = React . useState ( null ) ;
const [ analyticsEnabled , setAnalyticsEnabled ] = React . useState ( null ) ;
React . useEffect ( ( ) => {
async function init ( ) {
await dispatch . generalSettings . getGeneralSettings ( ) ;
setLoading ( false ) ;
}
2024-11-20 22:22:16 +01:00
2025-07-23 08:47:26 +02:00
init ( ) ;
} , [ ] ) ;
React . useEffect ( ( ) => {
async function init ( ) {
setInterval ( settings ? . interval ) ;
setPort ( settings ? . port ) ;
setWorkingHourFrom ( settings ? . workingHours ? . from ) ;
setWorkingHourTo ( settings ? . workingHours ? . to ) ;
setAnalyticsEnabled ( settings ? . analyticsEnabled || false ) ;
setDemoMode ( settings ? . demoMode || false ) ;
}
2024-11-20 22:22:16 +01:00
2025-07-23 08:47:26 +02:00
init ( ) ;
} , [ settings ] ) ;
2024-11-20 22:22:16 +01:00
2025-07-23 08:47:26 +02:00
const nullOrEmpty = ( val ) => val == null || val . length === 0 ;
2024-11-20 22:22:16 +01:00
2025-07-23 08:47:26 +02:00
const onStore = async ( ) => {
if ( nullOrEmpty ( interval ) ) {
2025-08-01 09:51:42 +02:00
Toast . error ( 'Interval may not be empty.' ) ;
2025-07-23 08:47:26 +02:00
return ;
}
if ( nullOrEmpty ( port ) ) {
2025-08-01 09:51:42 +02:00
Toast . error ( 'Port may not be empty.' ) ;
2025-07-23 08:47:26 +02:00
return ;
}
if (
( ! nullOrEmpty ( workingHourFrom ) && nullOrEmpty ( workingHourTo ) ) ||
( nullOrEmpty ( workingHourFrom ) && ! nullOrEmpty ( workingHourTo ) )
) {
2025-08-01 09:51:42 +02:00
Toast . error ( 'Working hours to and from must be set if either to or from has been set before.' ) ;
2025-07-23 08:47:26 +02:00
return ;
}
try {
await xhrPost ( '/api/admin/generalSettings' , {
interval ,
port ,
workingHours : {
from : workingHourFrom ,
to : workingHourTo ,
} ,
demoMode ,
analyticsEnabled ,
} ) ;
} catch ( exception ) {
console . error ( exception ) ;
if ( exception ? . json ? . message != null ) {
2025-08-01 09:51:42 +02:00
Toast . error ( exception . json . message ) ;
2025-07-23 08:47:26 +02:00
} else {
2025-08-01 09:51:42 +02:00
Toast . error ( 'Error while trying to store settings.' ) ;
2025-07-23 08:47:26 +02:00
}
return ;
}
2025-08-01 09:51:42 +02:00
Toast . success ( 'Settings stored successfully. We will reload your browser in 3 seconds.' ) ;
2025-07-23 08:47:26 +02:00
setTimeout ( ( ) => {
location . reload ( ) ;
} , 3000 ) ;
} ;
return (
< div >
{ ! loading && (
< React.Fragment >
< Headline text = "General Settings" / >
< div >
< SegmentPart
name = "Interval"
2025-09-08 08:30:45 +02:00
helpText = "Interval in minutes for running queries against the configured services. Do NOT go under 5 minutes as with a lower interval, your instance might be detected as a bot."
2025-07-23 08:47:26 +02:00
Icon = { IconRefresh }
>
< InputNumber
2025-09-08 08:30:45 +02:00
min = { 5 }
2025-07-23 08:47:26 +02:00
max = { 1440 }
placeholder = "Interval in minutes"
value = { interval }
formatter = { ( value ) => ` ${ value } ` . replace ( /\D/g , '' ) }
onChange = { ( value ) => setInterval ( value ) }
suffix = { 'minutes' }
/ >
< / SegmentPart >
< Divider margin = "1rem" / >
< SegmentPart name = "Port" helpText = "Port on which Fredy is running." Icon = { IconSignal } >
< InputNumber
min = { 0 }
max = { 99999 }
placeholder = "Port"
value = { port }
formatter = { ( value ) => ` ${ value } ` . replace ( /\D/g , '' ) }
onChange = { ( value ) => setPort ( value ) }
/ >
< / SegmentPart >
< Divider margin = "1rem" / >
< SegmentPart
name = "Working hours"
helpText = "During this hours, Fredy will search for new apartments. If nothing is configured, Fredy will search around the clock."
Icon = { IconCalendar }
>
< div className = "generalSettings__timePickerContainer" >
< TimePicker
format = { 'HH:mm' }
insetLabel = "From"
value = { formatFromTBackend ( workingHourFrom ) }
placeholder = ""
onChange = { ( val ) => {
setWorkingHourFrom ( val == null ? null : formatFromTimestamp ( val ) ) ;
} }
/ >
< TimePicker
format = { 'HH:mm' }
insetLabel = "Until"
value = { formatFromTBackend ( workingHourTo ) }
placeholder = ""
onChange = { ( val ) => {
setWorkingHourTo ( val == null ? null : formatFromTimestamp ( val ) ) ;
} }
/ >
< / div >
< / SegmentPart >
< Divider margin = "1rem" / >
< SegmentPart name = "Analytics" helpText = "Insights into the usage of Fredy." Icon = { IconLineChartStroked } >
< Banner
fullMode = { false }
type = "info"
closeIcon = { null }
title = { < div style = { { fontWeight : 600 , fontSize : '14px' , lineHeight : '20px' } } > Explanation < / div > }
style = { { marginBottom : '1rem' } }
description = {
< div >
Analytics are disabled by default . If you choose to enable them , we will begin tracking the
following :
< br / >
< ul >
< li > Name of active provider ( e . g . Immoscout ) < / li >
< li > Name of active adapter ( e . g . Console ) < / li >
< li > language < / li >
< li > os < / li >
< li > node version < / li >
< li > arch < / li >
< / ul >
The data is sent anonymously and helps me understand which providers or adapters are being used the
most . In the end it helps me to improve fredy .
< / div >
}
/ >
< Checkbox checked = { analyticsEnabled } onChange = { ( e ) => setAnalyticsEnabled ( e . target . checked ) } >
{ ' ' }
Enabled
< / Checkbox >
< / SegmentPart >
< Divider margin = "1rem" / >
< SegmentPart name = "Demo Mode" helpText = "If enabled, Fredy runs in demo mode." Icon = { IconSearch } >
< Banner
fullMode = { false }
type = "info"
closeIcon = { null }
title = { < div style = { { fontWeight : 600 , fontSize : '14px' , lineHeight : '20px' } } > Explanation < / div > }
style = { { marginBottom : '1rem' } }
description = {
< div >
In demo mode , Fredy will not ( really ) search for any real estates . Fredy is in a lockdown mode . Also
all database files will be set back to the default values at midnight .
< / div >
}
/ >
< Checkbox checked = { demoMode } onChange = { ( e ) => setDemoMode ( e . target . checked ) } >
{ ' ' }
Enabled
< / Checkbox >
< / SegmentPart >
< Divider margin = "1rem" / >
< Button type = "primary" theme = "solid" onClick = { onStore } icon = { < IconSave / > } >
Save
< / Button >
< / div >
< / React.Fragment >
) }
< / div >
) ;
2021-05-30 09:37:45 +02:00
} ;
export default GeneralSettings ;