Browse Source

Linter: manual fixes

Leszek Wiesner 4 years ago
parent
commit
a09a6c51fa

+ 23 - 6
pioneer/packages/joy-media/src/DiscoveryProvider.tsx

@@ -9,6 +9,7 @@ import ApiContext from '@polkadot/react-api/ApiContext';
 import { ApiProps } from '@polkadot/react-api/types';
 import { JoyInfo } from '@polkadot/joy-utils/react/components';
 import { componentName } from '@polkadot/joy-utils/react/helpers';
+import { isObjectWithProperties } from '@polkadot/joy-utils/functions/misc';
 
 export type BootstrapNodes = {
   bootstrapNodes?: Url[];
@@ -41,7 +42,7 @@ type ProviderStats = {
 }
 
 function newDiscoveryProvider ({ bootstrapNodes }: BootstrapNodes): DiscoveryProvider {
-  const stats: Map<string, ProviderStats> = new Map();
+  const stats = new Map<string, ProviderStats>();
 
   const resolveAssetEndpoint = async (storageProvider: StorageProviderId, contentId?: string, cancelToken?: CancelToken) => {
     const providerKey = storageProvider.toString();
@@ -67,14 +68,30 @@ function newDiscoveryProvider ({ bootstrapNodes }: BootstrapNodes): DiscoveryPro
         try {
           console.log(`Resolving ${providerKey} using ${discoveryUrl}`);
 
-          const serviceInfo = await axios.get(serviceInfoQuery, { cancelToken }) as any;
+          const serviceInfo = await axios.get<unknown>(serviceInfoQuery, { cancelToken });
 
           if (!serviceInfo) {
             continue;
           }
 
+          const { data } = serviceInfo;
+
+          if (!isObjectWithProperties(data, 'serialized') || typeof data.serialized !== 'string') {
+            continue;
+          }
+
+          const dataParsed = JSON.parse(data.serialized) as unknown;
+
+          if (
+            !isObjectWithProperties(dataParsed, 'asset') ||
+            !isObjectWithProperties(dataParsed.asset, 'endpoint') ||
+            typeof dataParsed.asset.endpoint !== 'string'
+          ) {
+            continue;
+          }
+
           stats.set(providerKey, {
-            assetApiEndpoint: normalizeUrl(JSON.parse(serviceInfo.data.serialized).asset.endpoint),
+            assetApiEndpoint: normalizeUrl(dataParsed.asset.endpoint),
             unreachableReports: 0,
             resolvedAt: Date.now()
           });
@@ -116,7 +133,7 @@ function newDiscoveryProvider ({ bootstrapNodes }: BootstrapNodes): DiscoveryPro
 
 const DiscoveryProviderContext = createContext<DiscoveryProvider>(undefined as unknown as DiscoveryProvider);
 
-export const DiscoveryProviderProvider = (props: React.PropsWithChildren<{}>) => {
+export const DiscoveryProviderProvider = (props: React.PropsWithChildren<Record<any, unknown>>) => {
   const api: ApiProps = useContext(ApiContext);
   const [provider, setProvider] = useState<DiscoveryProvider | undefined>();
   const [loaded, setLoaded] = useState<boolean | undefined>();
@@ -133,7 +150,7 @@ export const DiscoveryProviderProvider = (props: React.PropsWithChildren<{}>) =>
       console.log('Discovery Provider: Initialized');
     };
 
-    load();
+    void load();
   }, [loaded]);
 
   if (!api || !api.isApiReady) {
@@ -163,7 +180,7 @@ export const useDiscoveryProvider = () =>
   useContext(DiscoveryProviderContext);
 
 export function withDiscoveryProvider (Component: React.ComponentType<DiscoveryProviderProps>) {
-  const ResultComponent: React.FunctionComponent<{}> = (props: React.PropsWithChildren<{}>) => {
+  const ResultComponent: React.FunctionComponent<Record<any, unknown>> = (props: React.PropsWithChildren<Record<any, unknown>>) => {
     const discoveryProvider = useDiscoveryProvider();
 
     if (!discoveryProvider) {

+ 3 - 3
pioneer/packages/joy-media/src/IterableFile.ts

@@ -32,9 +32,9 @@ export class IterableFile implements AsyncIterable<Buffer> {
 
   readBlobAsBuffer (blob: Blob): Promise<Buffer> {
     return new Promise((resolve, reject) => {
-      this.reader.onload = (e: any) => {
-        e.target.result && resolve(Buffer.from(e.target.result));
-        e.target.error && reject(e.target.error);
+      this.reader.onload = (e) => {
+        e.target?.result && resolve(typeof e.target.result === 'string' ? Buffer.from(e.target.result) : Buffer.from(e.target.result));
+        e.target?.error && reject(e.target.error);
       };
 
       this.reader.readAsArrayBuffer(blob);

+ 6 - 5
pioneer/packages/joy-media/src/MediaView.tsx

@@ -6,6 +6,7 @@ import { useTransportContext } from './TransportContext';
 import { withMembershipRequired } from '@polkadot/joy-utils/react/hocs/guards';
 import { useApi } from '@polkadot/react-hooks';
 import { ApiPromise } from '@polkadot/api';
+import { isObjectWithProperties } from '@polkadot/joy-utils/functions/misc';
 
 type InitialPropsWithMembership<A> = A & {
   myAddress?: string;
@@ -32,11 +33,11 @@ type BaseProps<A, B> = {
   membersOnly?: boolean;
 }
 
-function serializeTrigger (val: any): any {
+function serializeTrigger (val: unknown): number | boolean | string | undefined {
   if (['number', 'boolean', 'string'].includes(typeof val)) {
-    return val;
-  } else if (typeof val === 'object' && typeof val.toString === 'function') {
-    return val.toString();
+    return val as number | boolean | string;
+  } else if (isObjectWithProperties(val, 'toString') && typeof val.toString === 'function') {
+    return val.toString() as string;
   } else {
     return undefined;
   }
@@ -74,7 +75,7 @@ export function MediaView<A extends Record<string, unknown> = Record<string, unk
       if (!transport) {
         console.error('Transport is not defined');
       } else {
-        doResolveProps();
+        void doResolveProps();
       }
     }, rerenderDeps);
 

+ 2 - 2
pioneer/packages/joy-media/src/TransportContext.tsx

@@ -10,12 +10,12 @@ export const TransportContext = createContext<MediaTransport>(undefined as unkno
 export const useTransportContext = () =>
   useContext(TransportContext);
 
-export const MockTransportProvider = (props: React.PropsWithChildren<{}>) =>
+export const MockTransportProvider = (props: React.PropsWithChildren<Record<any, unknown>>) =>
   <TransportContext.Provider value={new MockTransport()}>
     {props.children}
   </TransportContext.Provider>;
 
-export const SubstrateTransportProvider = (props: React.PropsWithChildren<{}>) => {
+export const SubstrateTransportProvider = (props: React.PropsWithChildren<Record<any, unknown>>) => {
   const api: ApiProps = useContext(ApiContext);
   const [transport, setTransport] = useState<SubstrateTransport>();
   const [loaded, setLoaded] = useState<boolean>();

+ 33 - 19
pioneer/packages/joy-media/src/Upload.tsx

@@ -1,6 +1,6 @@
 import React from 'react';
 import BN from 'bn.js';
-import axios, { CancelTokenSource } from 'axios';
+import axios, { CancelTokenSource, AxiosError, AxiosRequestConfig } from 'axios';
 import { History } from 'history';
 import { Progress, Message } from 'semantic-ui-react';
 
@@ -26,6 +26,7 @@ import { EditVideoView } from './upload/EditVideo.view';
 
 import { IterableFile } from './IterableFile';
 import { StorageProviderId } from '@joystream/types/working-group';
+import { normalizeError, isObjectWithProperties } from '@polkadot/joy-utils/functions/misc';
 
 const MAX_FILE_SIZE_MB = 500;
 const MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_MB * 1024 * 1024;
@@ -41,7 +42,7 @@ type Props = ApiProps & I18nProps & DiscoveryProviderProps & MyAccountProps & {
 };
 
 type State = {
-  error?: any;
+  error?: string;
   file?: File;
   computingHash: boolean;
   ipfs_cid?: string;
@@ -91,7 +92,7 @@ class Upload extends React.PureComponent<Props, State> {
   private renderContent () {
     const { error, uploading, discovering, computingHash, sendingTx } = this.state;
 
-    if (error) return this.renderError();
+    if (error) return this.renderError(error);
     else if (discovering) return this.renderDiscovering();
     else if (uploading) return this.renderUploading();
     else if (computingHash) return this.renderComputingHash();
@@ -99,13 +100,11 @@ class Upload extends React.PureComponent<Props, State> {
     else return this.renderFileInput();
   }
 
-  private renderError () {
-    const { error } = this.state;
-
+  private renderError (error: string) {
     return (
       <Message error className='JoyMainStatus'>
         <Message.Header>Failed to upload your file</Message.Header>
-        <p>{error.toString()}</p>
+        <p>{error}</p>
         <button className='ui button' onClick={this.resetForm}>Start over</button>
       </Message>
     );
@@ -223,7 +222,7 @@ class Upload extends React.PureComponent<Props, State> {
       });
     } else {
       this.setState({ file, computingHash: true });
-      this.startComputingHash();
+      void this.startComputingHash();
     }
   }
 
@@ -236,7 +235,8 @@ class Upload extends React.PureComponent<Props, State> {
 
     try {
       const iterableFile = new IterableFile(file, { chunkSize: 65535 });
-      const ipfs_cid = await IpfsHash.of(iterableFile);
+      // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call
+      const ipfs_cid = (await IpfsHash.of(iterableFile)) as string;
 
       this.hashComputationComplete(ipfs_cid);
     } catch (err) {
@@ -283,7 +283,7 @@ class Upload extends React.PureComponent<Props, State> {
       dataObject = await api.query.dataDirectory.dataObjectByContentId(newContentId) as Option<DataObject>;
     } catch (err) {
       this.setState({
-        error: err,
+        error: normalizeError(err),
         discovering: false
       });
 
@@ -299,10 +299,10 @@ class Upload extends React.PureComponent<Props, State> {
     if (dataObject.isSome) {
       const storageProvider = dataObject.unwrap().liaison;
 
-      this.uploadFileTo(storageProvider);
+      void this.uploadFileTo(storageProvider);
     } else {
       this.setState({
-        error: new Error('No Storage Provider assigned to process upload'),
+        error: 'No Storage Provider assigned to process upload',
         discovering: false
       });
     }
@@ -313,7 +313,7 @@ class Upload extends React.PureComponent<Props, State> {
 
     if (!file || !file.size) {
       this.setState({
-        error: new Error('No file to upload!'),
+        error: 'No file to upload!',
         discovering: false
       });
 
@@ -321,7 +321,7 @@ class Upload extends React.PureComponent<Props, State> {
     }
 
     const contentId = newContentId.encode();
-    const config = {
+    const config: AxiosRequestConfig = {
       headers: {
         // TODO uncomment this once the issue fixed:
         // https://github.com/Joystream/storage-node-joystream/issues/16
@@ -329,7 +329,15 @@ class Upload extends React.PureComponent<Props, State> {
         'Content-Type': '' // <-- this is a temporary hack
       },
       cancelToken: cancelSource.token,
-      onUploadProgress: (progressEvent: any) => {
+      onUploadProgress: (progressEvent: unknown) => {
+        if (
+          !isObjectWithProperties(progressEvent, 'loaded', 'total') ||
+          typeof progressEvent.loaded !== 'number' ||
+          typeof progressEvent.total !== 'number'
+        ) {
+          return;
+        }
+
         const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
 
         this.setState({
@@ -345,7 +353,7 @@ class Upload extends React.PureComponent<Props, State> {
       url = await discoveryProvider.resolveAssetEndpoint(storageProvider, contentId, cancelSource.token);
     } catch (err) {
       return this.setState({
-        error: new Error(`Failed to contact storage provider: ${err.message}`),
+        error: `Failed to contact storage provider: ${normalizeError(err)}`,
         discovering: false
       });
     }
@@ -362,14 +370,20 @@ class Upload extends React.PureComponent<Props, State> {
 
     try {
       await axios.put<{ message: string }>(url, file, config);
-    } catch (err) {
-      this.setState({ progress: 0, error: err, uploading: false });
+    } catch (e) {
+      const err = e as unknown;
+
+      this.setState({ progress: 0, error: normalizeError(err), uploading: false });
 
       if (axios.isCancel(err)) {
         return;
       }
 
-      if (!err.response || (err.response.status >= 500 && err.response.status <= 504)) {
+      const response = isObjectWithProperties(err, 'response')
+        ? (err as AxiosError).response
+        : undefined;
+
+      if (!response || (response.status >= 500 && response.status <= 504)) {
         // network connection error
         discoveryProvider.reportUnreachable(storageProvider);
       }

+ 1 - 1
pioneer/packages/joy-media/src/channels/ChannelsByOwner.view.tsx

@@ -18,7 +18,7 @@ export const ChannelsByOwnerView = MediaView<Props>({
   }
 });
 
-export const ChannelsByOwnerWithRouter = (props: Props & RouteComponentProps<any>) => {
+export const ChannelsByOwnerWithRouter = (props: Props & RouteComponentProps<Record<string, string | undefined>>) => {
   const { match: { params: { account } } } = props;
   const { api } = useApi();
 

+ 1 - 1
pioneer/packages/joy-media/src/channels/EditChannel.view.tsx

@@ -20,7 +20,7 @@ export const EditChannelView = MediaView<Props>({
   }
 });
 
-type WithRouterProps = Props & RouteComponentProps<any>
+type WithRouterProps = Props & RouteComponentProps<Record<string, string | undefined>>
 
 export const EditChannelWithRouter = (props: WithRouterProps) => {
   const { match: { params: { id } } } = props;

+ 1 - 1
pioneer/packages/joy-media/src/channels/ViewChannel.view.tsx

@@ -19,7 +19,7 @@ export const ViewChannelView = MediaView<Props>({
   }
 });
 
-export const ViewChannelWithRouter = (props: Props & RouteComponentProps<any>) => {
+export const ViewChannelWithRouter = (props: Props & RouteComponentProps<Record<string, string | undefined>>) => {
   const { match: { params: { id } } } = props;
   const { api } = useApi();
 

+ 1 - 1
pioneer/packages/joy-media/src/common/BgImg.tsx

@@ -13,7 +13,7 @@ type Props = {
 export function BgImg (props: Props) {
   let { url, width, height, size, circle, className, style } = props;
 
-  const fullClass = 'JoyBgImg ' + className;
+  const fullClass = `JoyBgImg ${className || ''}`;
 
   let fullStyle: CSSProperties = {
     backgroundImage: `url(${url})`

+ 5 - 5
pioneer/packages/joy-media/src/common/MediaForms.tsx

@@ -40,7 +40,7 @@ type MediaTextProps<OuterProps, FormValues> =
 type MediaFieldProps<OuterProps, FormValues> =
   BaseFieldProps<OuterProps, FormValues> &
   JoyForms.LabelledProps<FormValues> & {
-    fieldProps: any;
+    fieldProps: Record<string, unknown>;
   }
 
 type MediaDropdownProps<OuterProps, FormValues> =
@@ -106,8 +106,8 @@ export function withMediaForm<OuterProps, FormValues>
 
   const MediaDropdown = (props: MediaDropdownProps<OuterProps, FormValues>) => {
     const { field: f, options = [] } = props;
-    const id = f.id as string;
-    const value = (props.values as any)[id] || '';
+    const id = f.id;
+    const value = props.values[id] || '';
 
     return <MediaField {...props} fieldProps={{
       component: Dropdown,
@@ -116,10 +116,10 @@ export function withMediaForm<OuterProps, FormValues>
       options,
       value,
       onBlur: (_event: any, _data: DropdownProps) => {
-        props.setFieldTouched(id, true);
+        props.setFieldTouched(id as string, true);
       },
       onChange: (_event: any, data: DropdownProps) => {
-        props.setFieldValue(id, data.value);
+        props.setFieldValue(id as string, data.value);
       }
     }} />;
   };

+ 2 - 3
pioneer/packages/joy-media/src/common/MediaPlayerView.tsx

@@ -41,7 +41,7 @@ export type RequiredMediaPlayerProps = {
 type ContentProps = {
   contentType?: string;
   dataObjectOpt?: Option<DataObject>;
-  resolvedAssetUrl?: string;
+  resolvedAssetUrl: string;
 }
 
 type MediaPlayerViewProps = ApiProps & I18nProps &
@@ -103,6 +103,7 @@ function InnerComponent (props: MediaPlayerViewProps) {
   const { video, resolvedAssetUrl: url } = props;
 
   const { dataObjectOpt, channel } = props;
+  const { myAccountId } = useMyMembership();
 
   if (!dataObjectOpt || dataObjectOpt.isNone) {
     return null;
@@ -110,8 +111,6 @@ function InnerComponent (props: MediaPlayerViewProps) {
 
   // TODO extract and show the next info from dataObject:
   // {"owner":"5GSMNn8Sy8k64mGUWPDafjMZu9bQNX26GujbBQ1LeJpNbrfg","added_at":{"block":2781,"time":1582750854000},"type_id":1,"size":3664485,"liaison":"5HN528fspu4Jg3KXWm7Pu7aUK64RSBz2ZSbwo1XKR9iz3hdY","liaison_judgement":1,"ipfs_content_id":"QmNk4QczoJyPTAKdfoQna6KhAz3FwfjpKyRBXAZHG5djYZ"}
-
-  const { myAccountId } = useMyMembership();
   const iAmOwner = isAccountAChannelOwner(channel, myAccountId);
 
   return (

+ 21 - 9
pioneer/packages/joy-media/src/common/MediaPlayerWithResolver.tsx

@@ -1,11 +1,11 @@
 import React, { useState, useEffect } from 'react';
-import axios, { CancelTokenSource } from 'axios';
+import axios, { CancelTokenSource, AxiosError } from 'axios';
 import _ from 'lodash';
 
 import { ApiProps } from '@polkadot/react-api/types';
 import { I18nProps } from '@polkadot/react-components/types';
 import { withMulti } from '@polkadot/react-api/hoc';
-import { Option } from '@polkadot/types/codec';
+import { Option, Vec } from '@polkadot/types/codec';
 
 import translate from '../translate';
 import { DiscoveryProviderProps, withDiscoveryProvider } from '../DiscoveryProvider';
@@ -14,6 +14,7 @@ import { Message } from 'semantic-ui-react';
 import { MediaPlayerView, RequiredMediaPlayerProps } from './MediaPlayerView';
 import { JoyInfo } from '@polkadot/joy-utils/react/components';
 import { useTransport } from '@polkadot/joy-utils/react/hooks';
+import { isObjectWithProperties } from '@polkadot/joy-utils/functions/misc';
 
 type Props = ApiProps & I18nProps & DiscoveryProviderProps & RequiredMediaPlayerProps;
 
@@ -34,9 +35,13 @@ function InnerComponent (props: Props) {
     setError(undefined);
     setCancelSource(newCancelSource());
 
-    const rids: DataObjectStorageRelationshipId[] = await api.query.dataObjectStorageRegistry.relationshipsByContentId(contentId) as any;
+    const rids = await api.query.dataObjectStorageRegistry.relationshipsByContentId<Vec<DataObjectStorageRelationshipId>>(contentId);
 
-    const allRelationships: Option<DataObjectStorageRelationship>[] = await Promise.all(rids.map((id) => api.query.dataObjectStorageRegistry.relationships(id))) as any;
+    const allRelationships = await Promise.all(
+      rids.map((id) =>
+        api.query.dataObjectStorageRegistry.relationships<Option<DataObjectStorageRelationship>>(id)
+      )
+    );
 
     // Providers that have signalled onchain that they have the asset
     let readyProviders = allRelationships.filter((r) => r.isSome).map((r) => r.unwrap())
@@ -58,7 +63,7 @@ function InnerComponent (props: Props) {
     // are not pruned onchain.
     readyProviders = _.intersectionBy(activeProviders, readyProviders, (provider) => provider.toString());
 
-    console.log(`Found ${readyProviders.length} providers ready to serve content: ${readyProviders}`);
+    console.log(`Found ${readyProviders.length} providers ready to serve content: ${readyProviders.join(', ')}`);
 
     // shuffle to spread the load
     readyProviders = _.shuffle(readyProviders);
@@ -87,16 +92,23 @@ function InnerComponent (props: Props) {
       try {
         console.log('Check URL of resolved asset:', assetUrl);
         const response = await axios.head(assetUrl, { cancelToken: cancelSource.token });
+        const headers = response.headers as Record<string, string | undefined>;
 
-        setContentType(response.headers['content-type'] || 'video/video');
+        setContentType(headers['content-type'] || 'video/video');
         setResolvedAssetUrl(assetUrl);
 
         return;
-      } catch (err) {
+      } catch (e) {
+        const err = e as unknown;
+
         if (axios.isCancel(err)) {
           return;
         } else {
-          if (!err.response || (err.response.status >= 500 && err.response.status <= 504)) {
+          const response = isObjectWithProperties(err, 'response')
+            ? (err as AxiosError).response
+            : undefined;
+
+          if (!response || (response.status >= 500 && response.status <= 504)) {
             // network connection error
             discoveryProvider.reportUnreachable(provider);
           }
@@ -111,7 +123,7 @@ function InnerComponent (props: Props) {
   };
 
   useEffect(() => {
-    resolveAsset();
+    void resolveAsset();
 
     return () => {
       cancelSource.cancel();

+ 3 - 3
pioneer/packages/joy-media/src/common/TypeHelpers.ts

@@ -19,7 +19,7 @@ export function asChannelId (id: AnyChannelId): ChannelId {
   } else if (canBeId(id)) {
     return createMock('ChannelId', id);
   } else {
-    throw new Error(`Not supported format for Channel id: ${id}`);
+    throw new Error(`Not supported format for Channel id: ${typeof id === 'object' ? id.constructor.name : id}`);
   }
 }
 
@@ -29,7 +29,7 @@ export function asEntityId (id: AnyEntityId): EntityId {
   } else if (canBeId(id)) {
     return createMock('EntityId', id);
   } else {
-    throw new Error(`Not supported format for Entity id: ${id}`);
+    throw new Error(`Not supported format for Entity id: ${typeof id === 'object' ? id.constructor.name : id}`);
   }
 }
 
@@ -39,6 +39,6 @@ export function asClassId (id: AnyClassId): ClassId {
   } else if (canBeId(id)) {
     return createMock('ClassId', id);
   } else {
-    throw new Error(`Not supported format for Class id: ${id}`);
+    throw new Error(`Not supported format for Class id: ${typeof id === 'object' ? id.constructor.name : id}`);
   }
 }

+ 1 - 1
pioneer/packages/joy-media/src/index.tsx

@@ -26,7 +26,7 @@ import { AllVideosView } from './explore/AllVideos';
 import { AllChannelsView } from './explore/AllChannels';
 // import { VideosByOwner } from './video/VideosByOwner';
 
-type Props = AppProps & I18nProps & ApiProps & DiscoveryProviderProps & {};
+type Props = AppProps & I18nProps & ApiProps & DiscoveryProviderProps;
 
 function App (props: Props) {
   const { t, basePath } = props;

+ 1 - 1
pioneer/packages/joy-media/src/music/MusicTrackPreview.tsx

@@ -39,7 +39,7 @@ export function MusicTrackPreview (props: EditableMusicTrackPreviewProps) {
     setChecked(d.checked || false);
   };
 
-  return <div className={`JoyMusicTrackPreview ${checked && 'SelectedItem'} ${props.isDraggable && 'DraggableItem'}`}>
+  return <div className={`JoyMusicTrackPreview ${checked ? 'SelectedItem' : ''} ${props.isDraggable ? 'DraggableItem' : ''}`}>
     {props.onSelect && <div className='CheckboxCell'>
       <Checkbox checked={checked} onChange={onChange} />
     </div>}

+ 1 - 1
pioneer/packages/joy-media/src/music/MyMusicTracks.tsx

@@ -131,7 +131,7 @@ export function MyMusicTracks (props: MyMusicTracksProps) {
   const selectedTracks = tracks.filter((track) => idsOfSelectedTracks.has(track.id));
 
   const renderReorderTracks = () => {
-    return <Section title={`Add tracks to album "${albumName}"`}>
+    return <Section title={`Add tracks to album "${albumName || ''}"`}>
 
       <Message
         info

+ 1 - 1
pioneer/packages/joy-media/src/upload/EditVideo.view.tsx

@@ -23,7 +23,7 @@ export const EditVideoView = MediaView<Props>({
   }
 });
 
-type WithRouterProps = Props & RouteComponentProps<any>
+type WithRouterProps = Props & RouteComponentProps<Record<string, string | undefined>>
 
 export const UploadVideoWithRouter = (props: WithRouterProps) => {
   const { match: { params: { channelId } } } = props;

+ 5 - 3
pioneer/packages/joy-media/src/upload/UploadVideo.tsx

@@ -115,7 +115,7 @@ const InnerForm = (props: MediaFormProps<OuterProps, FormValues>) => {
     Object.keys(values).forEach((prop) => {
       const fieldName = prop as VideoPropId;
       const field = Fields[fieldName];
-      let fieldValue = values[fieldName] as any;
+      let fieldValue = values[fieldName];
 
       let shouldIncludeValue = true;
 
@@ -147,10 +147,12 @@ const InnerForm = (props: MediaFormProps<OuterProps, FormValues>) => {
         }
 
         if (isDateField(fieldName)) {
-          fieldValue = humanDateToUnixTs(fieldValue);
+          fieldValue = typeof fieldValue === 'string' ? (humanDateToUnixTs(fieldValue) || '') : '';
         }
 
-        res[fieldName] = fieldValue;
+        // FIXME: Temporary solution, since fixing this would require a bigger refactorization
+        // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+        res[fieldName] = fieldValue as any;
       }
     });
 

+ 1 - 1
pioneer/packages/joy-media/src/video/PlayVideo.view.tsx

@@ -27,7 +27,7 @@ export const PlayVideoView = MediaView<Props>({
   }
 });
 
-export const PlayVideoWithRouter = (props: Props & RouteComponentProps<any>) => {
+export const PlayVideoWithRouter = (props: Props & RouteComponentProps<Record<string, string | undefined>>) => {
   const { match: { params: { id } } } = props;
   const { api } = useApi();
 

+ 9 - 2
pioneer/packages/joy-utils/src/functions/misc.ts

@@ -167,13 +167,20 @@ export function bytesToString (bytes: Bytes) {
   return Buffer.from(bytes.toString().substr(2), 'hex').toString();
 }
 
-export function normalizeError (e: any): string {
+// Based on: https://fettblog.eu/typescript-hasownproperty/
+export function isObjectWithProperties<X extends unknown, Y extends PropertyKey[]> (input: X, ...props: Y): input is X & Record<Y[number], unknown> {
+  return typeof input === 'object' && input !== null && props.every((prop) => prop in input);
+}
+
+export function normalizeError (e: unknown): string {
   let message: string;
 
   if (e instanceof Error) {
     message = e.message;
+  } else if (isObjectWithProperties(e, 'toString') && typeof e.toString === 'function') {
+    message = e.toString() as string;
   } else if (typeof e === 'string') {
-    return e;
+    message = e;
   } else {
     message = JSON.stringify(e);
   }

+ 3 - 3
pioneer/packages/joy-utils/src/react/hocs/accounts.tsx

@@ -7,7 +7,7 @@ import { withCalls, withMulti, withObservable, ApiContext } from '@polkadot/reac
 import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
 
 import { MemberId, Membership } from '@joystream/types/members';
-import { LeadId, Lead, CuratorId, Curator } from '@joystream/types/content-working-group';
+import { LeadId, Lead, CuratorId, Curator, CurationActor } from '@joystream/types/content-working-group';
 
 import { queryMembershipToProp } from '@polkadot/joy-members/utils';
 import useMyAccount from '../hooks/useMyAccount';
@@ -35,9 +35,9 @@ export type MyAccountProps = MyAddressProps & {
   // Content Working Group
   isLeadSet?: Option<LeadId>;
   contentLeadId?: LeadId;
-  contentLead?: Lead; // linked_map value
+  contentLead?: Lead;
 
-  curationActor?: any;
+  curationActor?: [CurationActor, AccountId];
   allAccounts?: SubjectInfo;
 };
 

+ 3 - 3
pioneer/packages/joy-utils/src/transport/SimpleCache.ts

@@ -16,7 +16,7 @@ function anyIdToString (id: IdLike): string {
 export class SimpleCache<Id extends IdLike, Obj extends HasId> {
   private cacheName: string
   private loadByIds: LoadObjectsByIdsFn<Id, Obj>
-  private cache: Map<string, Obj> = new Map()
+  private cache = new Map<string, Obj>()
 
   constructor (cacheName: string, loadByIds: LoadObjectsByIdsFn<Id, Obj>) {
     this.cacheName = cacheName;
@@ -30,7 +30,7 @@ export class SimpleCache<Id extends IdLike, Obj extends HasId> {
   clear (): void {
     const prevCacheSize = this.cache.size;
 
-    this.cache = new Map();
+    this.cache = new Map<string, Obj>();
     console.info(`Removed all ${prevCacheSize} entries from ${this.cacheName}`);
   }
 
@@ -40,7 +40,7 @@ export class SimpleCache<Id extends IdLike, Obj extends HasId> {
       ? keepIds
       : new Set(keepIds.map((id) => id.toString()));
 
-    const newCache: Map<string, Obj> = new Map();
+    const newCache = new Map<string, Obj>();
 
     for (const [id, o] of this.cache.entries()) {
       if (keepIdsSet.has(id)) {