Skip to content

Commit

Permalink
feat: use context to manage popup screen
Browse files Browse the repository at this point in the history
  • Loading branch information
abhijithvijayan committed Jul 5, 2020
1 parent 5322e83 commit 42f7e3a
Show file tree
Hide file tree
Showing 5 changed files with 438 additions and 8 deletions.
6 changes: 4 additions & 2 deletions source/Popup/Form.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {useFormState} from 'react-use-form-state';
import React from 'react';
import tw from 'twin.macro';
import React from 'react';

const Form: React.FC = () => {
const [
Expand All @@ -25,7 +25,7 @@ const Form: React.FC = () => {
setFieldError: setFormStateFieldError,
} = formState;

const isFormValid =
const isFormValid: boolean =
((formStateValidity.customurl === undefined ||
formStateValidity.customurl) &&
(formStateValidity.password === undefined ||
Expand All @@ -36,6 +36,7 @@ const Form: React.FC = () => {

function handleCustomUrlInputChange(url: string): void {
setFormStateField('customurl', url);
// ToDo: Remove special symbols

if (url.length > 0 && url.length < 3) {
setFormStateFieldError(
Expand All @@ -47,6 +48,7 @@ const Form: React.FC = () => {

function handlePasswordInputChange(password: string): void {
setFormStateField('password', password);
// ToDo: Remove special symbols

if (password.length > 0 && password.length < 3) {
setFormStateFieldError(
Expand Down
203 changes: 201 additions & 2 deletions source/Popup/Popup.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,212 @@
import React from 'react';
import React, {useEffect} from 'react';

import {Kutt, UserSettingsResponseProperties} from '../Background';
import {openExtOptionsPage, isValidUrl} from '../util/tabs';
import {
ExtensionSettingsActionTypes,
DomainOptionsProperties,
useExtensionSettings,
HostProperties,
} from '../contexts/extension-settings-context';
import {
RequestStatusActionTypes,
useRequestStatus,
} from '../contexts/request-status-context';
import {
getExtensionSettings,
getPreviousSettings,
migrateSettings,
} from '../util/settings';

import BodyWrapper from '../components/BodyWrapper';
import Loader from '../components/Loader';
import Form from './Form';

const Popup: React.FC = () => {
const extensionSettingsDispatch = useExtensionSettings()[1];
const [requestStatusState, requestStatusDispatch] = useRequestStatus();

useEffect((): void => {
async function getUserSettings(): Promise<void> {
// -----------------------------------------------------------------------------//
// -----------------------------------------------------------------------------//
// ----- // ToDo: remove in next major release // ----- //
// ----- Ref: https://github.com/abhijithvijayan/kutt-extension/issues/78 ----- //
// -----------------------------------------------------------------------------//
// -----------------------------------------------------------------------------//

const {
// old keys from extension v3.x.x
key = '',
host = '',
userOptions = {
autoCopy: false,
devMode: false,
keepHistory: false,
pwdForUrls: false,
},
} = await getPreviousSettings();

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const migrationSettings: any = {};
let performMigration = false;

if (key.trim().length > 0) {
// map it to `settings.apikey`
migrationSettings.apikey = key;
performMigration = true;
}
if (host.trim().length > 0 && userOptions.devMode) {
// map `host` to `settings.customhost`
migrationSettings.customhost = host;
// set `advanced` to true
migrationSettings.advanced = true;
performMigration = true;
}
if (userOptions.keepHistory) {
// set `settings.history` to true
migrationSettings.history = true;
performMigration = true;
}
if (performMigration) {
// perform migration
await migrateSettings(migrationSettings);
}

// -----------------------------------------------------------------------------//
// -----------------------------------------------------------------------------//
// -----------------------------------------------------------------------------//
// -----------------------------------------------------------------------------//
// -----------------------------------------------------------------------------//
// -----------------------------------------------------------------------------//

// ToDo: set types: refer https://kutt.it/jITyIU
const {settings = {}} = await getExtensionSettings();

// No API Key set
if (
!Object.prototype.hasOwnProperty.call(settings, 'apikey') ||
settings.apikey === ''
) {
requestStatusDispatch({
type: RequestStatusActionTypes.SET_REQUEST_STATUS,
payload: {
error: true,
message: 'Extension requires an API Key to work',
},
});
requestStatusDispatch({
type: RequestStatusActionTypes.SET_LOADING,
payload: false,
});

// Open options page
setTimeout(() => {
return openExtOptionsPage();
}, 1300);

return;
}

let defaultHost: HostProperties = Kutt;

// If `advanced` field is true
if (
Object.prototype.hasOwnProperty.call(settings, 'advanced') &&
settings.advanced
) {
// If `customhost` field is set
if (
Object.prototype.hasOwnProperty.call(settings, 'customhost') &&
settings.customhost.trim().length > 0 &&
isValidUrl(settings.customhost)
) {
defaultHost = {
hostDomain: settings.customhost
.replace('http://', '')
.replace('https://', '')
.replace('www.', '')
.split(/[/?#]/)[0], // extract domain
hostUrl: settings.customhost.endsWith('/')
? settings.customhost.slice(0, -1)
: settings.customhost, // slice `/` at the end
};
}
}

// options menu
const defaultOptions: DomainOptionsProperties[] = [
{
id: '',
option: '-- Choose Domain --',
value: '',
disabled: true,
},
{
id: 'default',
option: defaultHost.hostDomain,
value: defaultHost.hostUrl,
disabled: false,
},
];

// `user` & `apikey` fields exist on storage
if (
Object.prototype.hasOwnProperty.call(settings, 'user') &&
settings.user
) {
const {user}: {user: UserSettingsResponseProperties} = settings;

let optionsList: DomainOptionsProperties[] = user.domains.map(
({id, address, homepage, banned}) => {
return {
id,
option: homepage,
value: address,
disabled: banned,
};
}
);

// merge to beginning of array
optionsList = defaultOptions.concat(optionsList);

// update domain list
extensionSettingsDispatch({
type: ExtensionSettingsActionTypes.SET_EXTENSION_SETTINGS,
payload: {
apikey: settings.apikey.trim(),
domainOptions: optionsList,
host: defaultHost,
},
});
} else {
// no `user` but `apikey` exist on storage
extensionSettingsDispatch({
type: ExtensionSettingsActionTypes.SET_EXTENSION_SETTINGS,
payload: {
apikey: settings.apikey.trim(),
domainOptions: defaultOptions,
host: defaultHost,
},
});
}

// stop loader
requestStatusDispatch({
type: RequestStatusActionTypes.SET_LOADING,
payload: false,
});
}

getUserSettings();
// ToDo: pageReloadFlag for updating ui when local storage settings are changed
}, [extensionSettingsDispatch, requestStatusDispatch]);

return (
<BodyWrapper>
<div id="popup">
<Form />
{!requestStatusState.loading ? <Form /> : <Loader />}
</div>
</BodyWrapper>
);
Expand Down
14 changes: 10 additions & 4 deletions source/Popup/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,26 @@ import ReactDOM from 'react-dom';

// Common styles
import '../styles/main.scss';

// ToDo: remove later
import './refactor/styles.scss';

import Popup from './Popup';
import {ExtensionSettingsProvider} from '../contexts/extension-settings-context';
import {RequestStatusProvider} from '../contexts/request-status-context';
import OtherPopup from './refactor/Popup';
import Popup from './Popup';

// eslint-disable-next-line import/no-webpack-loader-syntax, import/no-unresolved, @typescript-eslint/no-var-requires, node/no-missing-require
const theme = require('sass-extract-loader?{"plugins": ["sass-extract-js"]}!../styles/base/_variables.scss');
// Require sass variables using sass-extract-loader and specify the plugin

ReactDOM.render(
<ThemeProvider theme={theme}>
<Popup />
{/* <OtherPopup /> */}
<ExtensionSettingsProvider>
<RequestStatusProvider>
<Popup />
{/* <OtherPopup /> */}
</RequestStatusProvider>
</ExtensionSettingsProvider>
</ThemeProvider>,
document.getElementById('popup-root')
);
115 changes: 115 additions & 0 deletions source/contexts/extension-settings-context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/* eslint-disable @typescript-eslint/naming-convention */
import React, {createContext, useReducer, useContext} from 'react';

import {Kutt} from '../Background';

export enum ExtensionSettingsActionTypes {
SET_EXTENSION_SETTINGS = 'set-extension-settings',
}

export type HostProperties = {
hostDomain: string;
hostUrl: string;
};

export type DomainOptionsProperties = {
option: string;
value: string;
id: string;
disabled: boolean;
};

type SET_EXTENSION_SETTINGS = {
type: ExtensionSettingsActionTypes.SET_EXTENSION_SETTINGS;
payload: {
apikey: string;
domainOptions: DomainOptionsProperties[];
host: HostProperties;
};
};

type Action = SET_EXTENSION_SETTINGS;

type InitialValues = {
apikey: string;
domainOptions: DomainOptionsProperties[];
host: HostProperties;
};

const initialValues: InitialValues = {
apikey: '',
domainOptions: [],
host: Kutt,
};

type State = InitialValues;
type Dispatch = (action: Action) => void;

const ExtensionSettingsStateContext = createContext<State | undefined>(
undefined
);
const ExtensionSettingsDispatchContext = createContext<Dispatch | undefined>(
undefined
);

function extensionSettingsReducer(state: State, action: Action): State {
switch (action.type) {
case ExtensionSettingsActionTypes.SET_EXTENSION_SETTINGS: {
return {...state, ...action.payload};
}

default:
return state;
}
}

function useExtensionSettingsState(): State {
const context = useContext(ExtensionSettingsStateContext);

if (context === undefined) {
throw new Error(
'useExtensionSettingsState must be used within a ExtensionSettingsProvider'
);
}

return context;
}

function useExtensionSettingsDispatch(): Dispatch {
const context = useContext(ExtensionSettingsDispatchContext);

if (context === undefined) {
throw new Error(
'useExtensionSettingsDispatch must be used within a ExtensionSettingsProvider'
);
}

return context;
}

function useExtensionSettings(): [State, Dispatch] {
// To access const [state, dispatch] = useExtensionSettings()
return [useExtensionSettingsState(), useExtensionSettingsDispatch()];
}

type ExtensionSettingsProviderProps = {
children: React.ReactNode;
};

const ExtensionSettingsProvider: React.FC<ExtensionSettingsProviderProps> = ({
children,
}) => {
const [state, dispatch] = useReducer(extensionSettingsReducer, initialValues);

return (
<>
<ExtensionSettingsStateContext.Provider value={state}>
<ExtensionSettingsDispatchContext.Provider value={dispatch}>
{children}
</ExtensionSettingsDispatchContext.Provider>
</ExtensionSettingsStateContext.Provider>
</>
);
};

export {ExtensionSettingsProvider, useExtensionSettings};
Loading

0 comments on commit 42f7e3a

Please sign in to comment.