pl-fe: move around some files
Signed-off-by: Nicole Mikołajczyk <git@mkljczk.pl>
This commit is contained in:
@@ -1,205 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useIntl, FormattedMessage, defineMessages } from 'react-intl';
|
||||
|
||||
import { createApp } from 'pl-fe/actions/apps';
|
||||
import { obtainOAuthToken } from 'pl-fe/actions/oauth';
|
||||
import Button from 'pl-fe/components/ui/button';
|
||||
import Column from 'pl-fe/components/ui/column';
|
||||
import Form from 'pl-fe/components/ui/form';
|
||||
import FormActions from 'pl-fe/components/ui/form-actions';
|
||||
import FormGroup from 'pl-fe/components/ui/form-group';
|
||||
import Input from 'pl-fe/components/ui/input';
|
||||
import Stack from 'pl-fe/components/ui/stack';
|
||||
import Text from 'pl-fe/components/ui/text';
|
||||
import Textarea from 'pl-fe/components/ui/textarea';
|
||||
import { useOwnAccount } from 'pl-fe/hooks/use-own-account';
|
||||
import { getBaseURL } from 'pl-fe/utils/accounts';
|
||||
|
||||
import type { CredentialApplication, Token } from 'pl-api';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.app_create', defaultMessage: 'Create app' },
|
||||
namePlaceholder: { id: 'app_create.name_placeholder', defaultMessage: 'e.g. \'pl-fe\'' },
|
||||
scopesPlaceholder: { id: 'app_create.scopes_placeholder', defaultMessage: 'e.g. \'read write follow\'' },
|
||||
});
|
||||
|
||||
const BLANK_PARAMS = {
|
||||
client_name: '',
|
||||
redirect_uris: 'urn:ietf:wg:oauth:2.0:oob',
|
||||
scopes: '',
|
||||
website: '',
|
||||
};
|
||||
|
||||
type Params = typeof BLANK_PARAMS;
|
||||
|
||||
const CreateApp: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const { account } = useOwnAccount();
|
||||
|
||||
const [app, setApp] = useState<Record<string, any> | null>(null);
|
||||
const [token, setToken] = useState<Token | null>(null);
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
const [params, setParams] = useState<Params>(BLANK_PARAMS);
|
||||
|
||||
const handleCreateApp = () => {
|
||||
const baseURL = getBaseURL(account!);
|
||||
|
||||
return createApp(params, baseURL)
|
||||
.then(app => {
|
||||
setApp(app);
|
||||
return app;
|
||||
});
|
||||
};
|
||||
|
||||
const handleCreateToken = (app: CredentialApplication) => {
|
||||
const baseURL = getBaseURL(account!);
|
||||
|
||||
const tokenParams = {
|
||||
client_id: app!.client_id,
|
||||
client_secret: app!.client_secret,
|
||||
redirect_uri: params.redirect_uris,
|
||||
grant_type: 'client_credentials',
|
||||
scope: params.scopes,
|
||||
};
|
||||
|
||||
return obtainOAuthToken(tokenParams, baseURL)
|
||||
.then(setToken);
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
setLoading(true);
|
||||
|
||||
handleCreateApp()
|
||||
.then(handleCreateToken)
|
||||
.then(() => {
|
||||
scrollToTop();
|
||||
setLoading(false);
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const setParam = (key: string, value: string) => {
|
||||
setParams({ ...params, [key]: value });
|
||||
};
|
||||
|
||||
const handleParamChange = (key: string): React.ChangeEventHandler<HTMLInputElement> => e => {
|
||||
setParam(key, e.target.value);
|
||||
};
|
||||
|
||||
const resetState = () => {
|
||||
setApp(null);
|
||||
setToken(null);
|
||||
setLoading(false);
|
||||
setParams(BLANK_PARAMS);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
resetState();
|
||||
scrollToTop();
|
||||
};
|
||||
|
||||
const scrollToTop = (): void => {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
};
|
||||
|
||||
const renderResults = () => (
|
||||
<Column label={intl.formatMessage(messages.heading)} backHref='/developers'>
|
||||
<Form>
|
||||
<Stack>
|
||||
<Text size='lg' weight='medium'>
|
||||
<FormattedMessage id='app_create.results.explanation_title' defaultMessage='App created successfully' />
|
||||
</Text>
|
||||
<Text theme='muted'>
|
||||
<FormattedMessage
|
||||
id='app_create.results.explanation_text'
|
||||
defaultMessage='You created a new app and token! Please copy the credentials somewhere; you will not see them again after navigating away from this page.'
|
||||
/>
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
<FormGroup labelText={<FormattedMessage id='app_create.results.app_label' defaultMessage='App' />}>
|
||||
<Textarea
|
||||
value={JSON.stringify(app, null, 2)}
|
||||
rows={10}
|
||||
readOnly
|
||||
isCodeEditor
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup labelText={<FormattedMessage id='app_create.results.token_label' defaultMessage='OAuth token' />}>
|
||||
<Textarea
|
||||
value={JSON.stringify(token, null, 2)}
|
||||
rows={10}
|
||||
readOnly
|
||||
isCodeEditor
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormActions>
|
||||
<Button theme='primary' type='button' onClick={handleReset}>
|
||||
<FormattedMessage id='app_create.restart' defaultMessage='Create another' />
|
||||
</Button>
|
||||
</FormActions>
|
||||
</Form>
|
||||
</Column>
|
||||
);
|
||||
|
||||
if (app && token) {
|
||||
return renderResults();
|
||||
}
|
||||
|
||||
return (
|
||||
<Column label={intl.formatMessage(messages.heading)} backHref='/developers'>
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<FormGroup labelText={<FormattedMessage id='app_create.name_label' defaultMessage='App name' />}>
|
||||
<Input
|
||||
type='text'
|
||||
placeholder={intl.formatMessage(messages.namePlaceholder)}
|
||||
onChange={handleParamChange('client_name')}
|
||||
value={params.client_name}
|
||||
required
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup labelText={<FormattedMessage id='app_create.website_label' defaultMessage='Website' />}>
|
||||
<Input
|
||||
type='text'
|
||||
placeholder='https://github.com/mkljczk/pl-fe'
|
||||
onChange={handleParamChange('website')}
|
||||
value={params.website}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup labelText={<FormattedMessage id='app_create.redirect_uri_label' defaultMessage='Redirect URIs' />}>
|
||||
<Input
|
||||
type='text'
|
||||
placeholder='https://github.com/mkljczk/pl-fe'
|
||||
onChange={handleParamChange('redirect_uris')}
|
||||
value={params.redirect_uris}
|
||||
required
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup labelText={<FormattedMessage id='app_create.scopes_label' defaultMessage='Scopes' />}>
|
||||
<Input
|
||||
type='text'
|
||||
placeholder={intl.formatMessage(messages.scopesPlaceholder)}
|
||||
onChange={handleParamChange('scopes')}
|
||||
value={params.scopes}
|
||||
required
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormActions>
|
||||
<Button theme='primary' type='submit' disabled={isLoading}>
|
||||
<FormattedMessage id='app_create.submit' defaultMessage='Create app' />
|
||||
</Button>
|
||||
</FormActions>
|
||||
</Form>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
export { CreateApp as default };
|
||||
@@ -1,114 +0,0 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import Column from 'pl-fe/components/ui/column';
|
||||
import SvgIcon from 'pl-fe/components/ui/svg-icon';
|
||||
import Text from 'pl-fe/components/ui/text';
|
||||
import toast from 'pl-fe/toast';
|
||||
import sourceCode from 'pl-fe/utils/code';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.developers', defaultMessage: 'Developers' },
|
||||
});
|
||||
|
||||
interface IDashWidget {
|
||||
to?: string;
|
||||
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const DashWidget: React.FC<IDashWidget> = ({ to, onClick, children }) => {
|
||||
const className = 'bg-gray-200 dark:bg-gray-800 hover:bg-gray-300 dark:hover:bg-gray-800/75 p-4 rounded flex flex-col items-center justify-center space-y-2';
|
||||
|
||||
if (to) {
|
||||
return <Link className={className} to={to}>{children}</Link>;
|
||||
} else {
|
||||
return <button className={className} onClick={onClick}>{children}</button>;
|
||||
}
|
||||
};
|
||||
|
||||
const Developers: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
const showToast = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
event.preventDefault();
|
||||
|
||||
toast.success('Hello world!', {
|
||||
action: () => alert('hi'),
|
||||
actionLabel: 'Click me',
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Column label={intl.formatMessage(messages.heading)}>
|
||||
<div className='grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-3'>
|
||||
<DashWidget to='/developers/apps/create'>
|
||||
<SvgIcon src={require('@tabler/icons/outline/apps.svg')} className='text-gray-700 dark:text-gray-600' />
|
||||
|
||||
<Text>
|
||||
<FormattedMessage id='developers.navigation.app_create_label' defaultMessage='Create an app' />
|
||||
</Text>
|
||||
</DashWidget>
|
||||
|
||||
<DashWidget to='/developers/settings_store'>
|
||||
<SvgIcon src={require('@tabler/icons/outline/code-plus.svg')} className='text-gray-700 dark:text-gray-600' />
|
||||
|
||||
<Text>
|
||||
<FormattedMessage id='developers.navigation.settings_store_label' defaultMessage='Settings store' />
|
||||
</Text>
|
||||
</DashWidget>
|
||||
|
||||
<DashWidget to='/developers/timeline'>
|
||||
<SvgIcon src={require('@tabler/icons/outline/home.svg')} className='text-gray-700 dark:text-gray-600' />
|
||||
|
||||
<Text>
|
||||
<FormattedMessage id='developers.navigation.test_timeline_label' defaultMessage='Test timeline' />
|
||||
</Text>
|
||||
</DashWidget>
|
||||
|
||||
<DashWidget to='/error'>
|
||||
<SvgIcon src={require('@tabler/icons/outline/mood-sad.svg')} className='text-gray-700 dark:text-gray-600' />
|
||||
|
||||
<Text>
|
||||
<FormattedMessage id='developers.navigation.intentional_error_label' defaultMessage='Trigger an error' />
|
||||
</Text>
|
||||
</DashWidget>
|
||||
|
||||
<DashWidget to='/error/network'>
|
||||
<SvgIcon src={require('@tabler/icons/outline/refresh.svg')} className='text-gray-700 dark:text-gray-600' />
|
||||
|
||||
<Text>
|
||||
<FormattedMessage id='developers.navigation.network_error_label' defaultMessage='Network error' />
|
||||
</Text>
|
||||
</DashWidget>
|
||||
|
||||
<DashWidget to='/developers/sw'>
|
||||
<SvgIcon src={require('@tabler/icons/outline/script.svg')} className='text-gray-700 dark:text-gray-600' />
|
||||
|
||||
<Text>
|
||||
<FormattedMessage id='developers.navigation.service_worker_label' defaultMessage='Service Worker' />
|
||||
</Text>
|
||||
</DashWidget>
|
||||
|
||||
<DashWidget onClick={showToast}>
|
||||
<SvgIcon src={require('@tabler/icons/outline/urgent.svg')} className='text-gray-700 dark:text-gray-600' />
|
||||
|
||||
<Text>
|
||||
<FormattedMessage id='developers.navigation.show_toast' defaultMessage='Trigger Toast' />
|
||||
</Text>
|
||||
</DashWidget>
|
||||
</div>
|
||||
</Column>
|
||||
|
||||
<div className='p-4'>
|
||||
<Text align='center' theme='subtle' size='sm'>
|
||||
{sourceCode.displayName} {sourceCode.version}
|
||||
</Text>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export { Developers as default };
|
||||
@@ -1,146 +0,0 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import List, { ListItem } from 'pl-fe/components/list';
|
||||
import Button from 'pl-fe/components/ui/button';
|
||||
import Column from 'pl-fe/components/ui/column';
|
||||
import FormActions from 'pl-fe/components/ui/form-actions';
|
||||
import HStack from 'pl-fe/components/ui/hstack';
|
||||
import Icon from 'pl-fe/components/ui/icon';
|
||||
import Stack from 'pl-fe/components/ui/stack';
|
||||
import Text from 'pl-fe/components/ui/text';
|
||||
import { unregisterSW } from 'pl-fe/utils/sw';
|
||||
|
||||
import Indicator from './components/indicator';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.developers.service_worker', defaultMessage: 'Service Worker' },
|
||||
status: { id: 'sw.status', defaultMessage: 'Status' },
|
||||
url: { id: 'sw.url', defaultMessage: 'Script URL' },
|
||||
});
|
||||
|
||||
/** Hook that returns the active ServiceWorker registration. */
|
||||
const useRegistration = () => {
|
||||
const [isLoading, setLoading] = useState(true);
|
||||
const [registration, setRegistration] = useState<ServiceWorkerRegistration>();
|
||||
|
||||
const isSupported = 'serviceWorker' in navigator;
|
||||
|
||||
useEffect(() => {
|
||||
if (isSupported) {
|
||||
navigator.serviceWorker.getRegistration()
|
||||
.then(r => {
|
||||
setRegistration(r);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(() => setLoading(false));
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
registration,
|
||||
};
|
||||
};
|
||||
|
||||
interface IServiceWorkerInfo {
|
||||
}
|
||||
|
||||
/** Mini ServiceWorker debugging component. */
|
||||
const ServiceWorkerInfo: React.FC<IServiceWorkerInfo> = () => {
|
||||
const intl = useIntl();
|
||||
const { isLoading, registration } = useRegistration();
|
||||
|
||||
const url = registration?.active?.scriptURL;
|
||||
|
||||
const getState = () => {
|
||||
if (registration?.waiting) {
|
||||
return 'pending';
|
||||
} else if (registration?.active) {
|
||||
return 'active';
|
||||
} else {
|
||||
return 'inactive';
|
||||
}
|
||||
};
|
||||
|
||||
const getMessage = () => {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='sw.state.loading'
|
||||
defaultMessage='Loading…'
|
||||
/>
|
||||
);
|
||||
} else if (!isLoading && !registration) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='sw.state.unavailable'
|
||||
defaultMessage='Unavailable'
|
||||
/>
|
||||
);
|
||||
} else if (registration?.waiting) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='sw.state.waiting'
|
||||
defaultMessage='Waiting'
|
||||
/>
|
||||
);
|
||||
} else if (registration?.active) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='sw.state.active'
|
||||
defaultMessage='Active'
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='sw.state.unknown'
|
||||
defaultMessage='Unknown'
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRestart = async() => {
|
||||
await unregisterSW();
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
return (
|
||||
<Column label={intl.formatMessage(messages.heading)} backHref='/developers'>
|
||||
<Stack space={4}>
|
||||
<List>
|
||||
<ListItem label={intl.formatMessage(messages.status)}>
|
||||
<HStack alignItems='center' space={2}>
|
||||
<Indicator state={getState()} />
|
||||
<Text size='md' theme='muted'>{getMessage()}</Text>
|
||||
</HStack>
|
||||
</ListItem>
|
||||
|
||||
{url && (
|
||||
<ListItem label={intl.formatMessage(messages.url)}>
|
||||
<a href={url} target='_blank' className='flex items-center space-x-1 truncate'>
|
||||
<span className='truncate'>{url}</span>
|
||||
<Icon
|
||||
className='size-4'
|
||||
src={require('@tabler/icons/outline/external-link.svg')}
|
||||
/>
|
||||
</a>
|
||||
</ListItem>
|
||||
)}
|
||||
</List>
|
||||
|
||||
<FormActions>
|
||||
<Button theme='tertiary' type='button' onClick={handleRestart}>
|
||||
<FormattedMessage id='sw.restart' defaultMessage='Restart' />
|
||||
</Button>
|
||||
</FormActions>
|
||||
</Stack>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
export { ServiceWorkerInfo as default };
|
||||
@@ -1,109 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useIntl, FormattedMessage, defineMessages } from 'react-intl';
|
||||
|
||||
import { changeSetting, updateSettingsStore } from 'pl-fe/actions/settings';
|
||||
import List, { ListItem } from 'pl-fe/components/list';
|
||||
import Button from 'pl-fe/components/ui/button';
|
||||
import { CardHeader, CardTitle } from 'pl-fe/components/ui/card';
|
||||
import Column from 'pl-fe/components/ui/column';
|
||||
import Form from 'pl-fe/components/ui/form';
|
||||
import FormActions from 'pl-fe/components/ui/form-actions';
|
||||
import FormGroup from 'pl-fe/components/ui/form-group';
|
||||
import Textarea from 'pl-fe/components/ui/textarea';
|
||||
import SettingToggle from 'pl-fe/features/settings/components/setting-toggle';
|
||||
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
||||
import { useSettingsStore } from 'pl-fe/stores/settings';
|
||||
import toast from 'pl-fe/toast';
|
||||
|
||||
const isJSONValid = (text: any): boolean => {
|
||||
try {
|
||||
JSON.parse(text);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.settings_store', defaultMessage: 'Settings store' },
|
||||
advanced: { id: 'developers.settings_store.advanced', defaultMessage: 'Advanced settings' },
|
||||
hint: { id: 'developers.settings_store.hint', defaultMessage: 'It is possible to directly edit your user settings here. BE CAREFUL! Editing this section can break your account, and you will only be able to recover through the API.' },
|
||||
});
|
||||
|
||||
const SettingsStore: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const { settings, userSettings, loadUserSettings } = useSettingsStore();
|
||||
|
||||
const [rawJSON, setRawJSON] = useState<string>(JSON.stringify(userSettings, null, 2));
|
||||
const [jsonValid, setJsonValid] = useState(true);
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
const handleEditJSON: React.ChangeEventHandler<HTMLTextAreaElement> = ({ target }) => {
|
||||
const rawJSON = target.value;
|
||||
setRawJSON(rawJSON);
|
||||
setJsonValid(isJSONValid(rawJSON));
|
||||
};
|
||||
|
||||
const onToggleChange = (key: string[], checked: boolean) => {
|
||||
dispatch(changeSetting(key, checked, { showAlert: true }));
|
||||
};
|
||||
|
||||
const handleSubmit: React.FormEventHandler = e => {
|
||||
const settings = JSON.parse(rawJSON);
|
||||
|
||||
setLoading(true);
|
||||
dispatch(updateSettingsStore(settings)).then(() => {
|
||||
loadUserSettings(settings);
|
||||
setLoading(false);
|
||||
}).catch(error => {
|
||||
toast.showAlertForError(error);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setRawJSON(JSON.stringify(userSettings, null, 2));
|
||||
setJsonValid(true);
|
||||
}, [userSettings]);
|
||||
|
||||
return (
|
||||
<Column label={intl.formatMessage(messages.heading)} backHref='/developers'>
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<FormGroup
|
||||
hintText={intl.formatMessage(messages.hint)}
|
||||
errors={jsonValid ? [] : ['is invalid']}
|
||||
>
|
||||
<Textarea
|
||||
value={rawJSON}
|
||||
onChange={handleEditJSON}
|
||||
disabled={isLoading}
|
||||
rows={12}
|
||||
isCodeEditor
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormActions>
|
||||
<Button theme='primary' type='submit' disabled={!jsonValid || isLoading}>
|
||||
<FormattedMessage id='plfe_config.save' defaultMessage='Save' />
|
||||
</Button>
|
||||
</FormActions>
|
||||
</Form>
|
||||
|
||||
<CardHeader className='mb-4'>
|
||||
<CardTitle title={intl.formatMessage(messages.advanced)} />
|
||||
</CardHeader>
|
||||
|
||||
<List>
|
||||
<ListItem
|
||||
label={<FormattedMessage id='preferences.fields.demo_label' defaultMessage='Demo mode' />}
|
||||
hint={<FormattedMessage id='preferences.fields.demo_hint' defaultMessage='Use the default pl-fe logo and color scheme. Useful for taking screenshots.' />}
|
||||
>
|
||||
<SettingToggle settings={settings} settingPath={['demo']} onChange={onToggleChange} />
|
||||
</ListItem>
|
||||
</List>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
export { SettingsStore as default };
|
||||
Reference in New Issue
Block a user