-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #357 from acelaya-forks/feature/settings-components
Feature/settings components
- Loading branch information
Showing
58 changed files
with
1,195 additions
and
135 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { DropdownBtn } from '@shlinkio/shlink-frontend-kit'; | ||
import type { FC } from 'react'; | ||
import { DropdownItem } from 'reactstrap'; | ||
import type { VisitsSettings } from '..'; | ||
|
||
export type DateInterval = VisitsSettings['defaultInterval']; | ||
|
||
export interface DateIntervalSelectorProps { | ||
active?: DateInterval; | ||
allText: string; | ||
onChange: (interval: DateInterval) => void; | ||
} | ||
|
||
export const INTERVAL_TO_STRING_MAP: Record<Exclude<DateInterval, 'all'>, string> = { | ||
today: 'Today', | ||
yesterday: 'Yesterday', | ||
last7Days: 'Last 7 days', | ||
last30Days: 'Last 30 days', | ||
last90Days: 'Last 90 days', | ||
last180Days: 'Last 180 days', | ||
last365Days: 'Last 365 days', | ||
}; | ||
|
||
const intervalToString = (interval: DateInterval | undefined, fallback: string): string => { | ||
if (!interval || interval === 'all') { | ||
return fallback; | ||
} | ||
|
||
return INTERVAL_TO_STRING_MAP[interval]; | ||
}; | ||
|
||
export const DateIntervalSelector: FC<DateIntervalSelectorProps> = ({ onChange, active, allText }) => ( | ||
<DropdownBtn text={intervalToString(active, allText)}> | ||
<DropdownItem active={active === 'all'} onClick={() => onChange('all')}> | ||
{allText} | ||
</DropdownItem> | ||
<DropdownItem divider /> | ||
{Object.entries(INTERVAL_TO_STRING_MAP).map( | ||
([interval, name]) => ( | ||
<DropdownItem key={interval} active={active === interval} onClick={() => onChange(interval as DateInterval)}> | ||
{name} | ||
</DropdownItem> | ||
), | ||
)} | ||
</DropdownBtn> | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import type { FC, PropsWithChildren } from 'react'; | ||
|
||
export const FormText: FC<PropsWithChildren> = ({ children }) => ( | ||
<small className="form-text text-muted d-block">{children}</small> | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { LabeledFormGroup, SimpleCard, ToggleSwitch } from '@shlinkio/shlink-frontend-kit'; | ||
import { clsx } from 'clsx'; | ||
import { useId } from 'react'; | ||
import { FormGroup, Input } from 'reactstrap'; | ||
import { useSetting } from '..'; | ||
import { FormText } from './FormText'; | ||
|
||
export type RealTimeUpdatesProps = { | ||
toggleRealTimeUpdates: (enabled: boolean) => void; | ||
setRealTimeUpdatesInterval: (interval: number) => void; | ||
}; | ||
|
||
export const RealTimeUpdatesSettings = ( | ||
{ toggleRealTimeUpdates, setRealTimeUpdatesInterval }: RealTimeUpdatesProps, | ||
) => { | ||
const { enabled, interval } = useSetting('realTimeUpdates', { enabled: true }); | ||
const inputId = useId(); | ||
|
||
return ( | ||
<SimpleCard title="Real-time updates" className="h-100"> | ||
<FormGroup> | ||
<ToggleSwitch checked={enabled} onChange={toggleRealTimeUpdates}> | ||
Enable or disable real-time updates. | ||
<FormText> | ||
Real-time updates are currently being <b>{enabled ? 'processed' : 'ignored'}</b>. | ||
</FormText> | ||
</ToggleSwitch> | ||
</FormGroup> | ||
<LabeledFormGroup | ||
noMargin | ||
label="Real-time updates frequency (in minutes):" | ||
labelClassName={clsx('form-label', { 'text-muted': !enabled })} | ||
id={inputId} | ||
> | ||
<Input | ||
type="number" | ||
min={0} | ||
placeholder="Immediate" | ||
disabled={!enabled} | ||
value={`${interval ?? ''}`} | ||
id={inputId} | ||
onChange={({ target }) => setRealTimeUpdatesInterval(Number(target.value))} | ||
/> | ||
{enabled && ( | ||
<FormText> | ||
{interval ? ( | ||
<span> | ||
Updates will be reflected in the UI | ||
every <b>{interval}</b> minute{interval > 1 && 's'}. | ||
</span> | ||
) : 'Updates will be reflected in the UI as soon as they happen.'} | ||
</FormText> | ||
)} | ||
</LabeledFormGroup> | ||
</SimpleCard> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import { mergeDeepRight } from '@shlinkio/data-manipulation'; | ||
import { NavPillItem, NavPills } from '@shlinkio/shlink-frontend-kit'; | ||
import type { FC, ReactNode } from 'react'; | ||
import { useCallback } from 'react'; | ||
import { Navigate, Route, Routes } from 'react-router-dom'; | ||
import type { DeepPartial } from '../../utils/types'; | ||
import { SettingsProvider } from '..'; | ||
import type { RealTimeUpdatesSettings, Settings, ShortUrlsListSettings } from '../types'; | ||
import { RealTimeUpdatesSettings as RealTimeUpdates } from './RealTimeUpdatesSettings'; | ||
import { ShortUrlCreationSettings as ShortUrlCreation } from './ShortUrlCreationSettings'; | ||
import { ShortUrlsListSettings as ShortUrlsList } from './ShortUrlsListSettings'; | ||
import { TagsSettings as Tags } from './TagsSettings'; | ||
import { UserInterfaceSettings } from './UserInterfaceSettings'; | ||
import { VisitsSettings as Visits } from './VisitsSettings'; | ||
|
||
export type ShlinkWebSettingsProps = { | ||
settings: Settings; | ||
defaultShortUrlsListOrdering: NonNullable<ShortUrlsListSettings['defaultOrdering']>; | ||
updateSettings: (settings: Settings) => void; | ||
}; | ||
|
||
const SettingsSections: FC<{ items: ReactNode[] }> = ({ items }) => ( | ||
<> | ||
{items.map((child, index) => <div key={index} className="mb-3">{child}</div>)} | ||
</> | ||
); | ||
|
||
export const ShlinkWebSettings: FC<ShlinkWebSettingsProps> = ( | ||
{ settings, updateSettings, defaultShortUrlsListOrdering }, | ||
) => { | ||
const updatePartialSettings = useCallback( | ||
(partialSettings: DeepPartial<Settings>) => updateSettings(mergeDeepRight(settings, partialSettings)), | ||
[settings, updateSettings], | ||
); | ||
const toggleRealTimeUpdates = useCallback( | ||
(enabled: boolean) => updatePartialSettings({ realTimeUpdates: { enabled } }), | ||
[updatePartialSettings], | ||
); | ||
const setRealTimeUpdatesInterval = useCallback( | ||
(interval: number) => updatePartialSettings({ realTimeUpdates: { interval } as RealTimeUpdatesSettings }), | ||
[updatePartialSettings], | ||
); | ||
const updateSettingsProp = useCallback( | ||
<Prop extends keyof Settings>(prop: Prop, value: Settings[Prop]) => updatePartialSettings({ [prop]: value }), | ||
[updatePartialSettings], | ||
); | ||
|
||
return ( | ||
<SettingsProvider value={settings}> | ||
<NavPills className="mb-3"> | ||
<NavPillItem to="general">General</NavPillItem> | ||
<NavPillItem to="short-urls">Short URLs</NavPillItem> | ||
<NavPillItem to="other-items">Other items</NavPillItem> | ||
</NavPills> | ||
|
||
<Routes> | ||
<Route | ||
path="general" | ||
element={( | ||
<SettingsSections | ||
items={[ | ||
<UserInterfaceSettings updateUiSettings={(v) => updateSettingsProp('ui', v)} />, | ||
<RealTimeUpdates | ||
toggleRealTimeUpdates={toggleRealTimeUpdates} | ||
setRealTimeUpdatesInterval={setRealTimeUpdatesInterval} | ||
/>, | ||
]} | ||
/> | ||
)} | ||
/> | ||
<Route | ||
path="short-urls" | ||
element={( | ||
<SettingsSections | ||
items={[ | ||
<ShortUrlCreation updateShortUrlCreationSettings={(v) => updateSettingsProp('shortUrlCreation', v)} />, | ||
<ShortUrlsList | ||
defaultOrdering={defaultShortUrlsListOrdering} | ||
updateShortUrlsListSettings={(v) => updateSettingsProp('shortUrlsList', v)} | ||
/>, | ||
]} | ||
/> | ||
)} | ||
/> | ||
<Route | ||
path="other-items" | ||
element={( | ||
<SettingsSections | ||
items={[ | ||
<Tags updateTagsSettings={(v) => updateSettingsProp('tags', v)} />, | ||
<Visits updateVisitsSettings={(v) => updateSettingsProp('visits', v)} />, | ||
]} | ||
/> | ||
)} | ||
/> | ||
<Route path="*" element={<Navigate replace to="general" />} /> | ||
</Routes> | ||
</SettingsProvider> | ||
); | ||
}; |
Oops, something went wrong.