Przeglądaj źródła

upload tweaks & others merge

upload tweaks & others deployment
Klaudiusz Dembler 3 lat temu
rodzic
commit
a4029d7fab
100 zmienionych plików z 589 dodań i 399 usunięć
  1. 10 9
      .env
  2. 3 0
      .gitignore
  3. 3 0
      .prettierrc.js
  4. 1 0
      .storybook/manager.js
  5. 6 2
      .storybook/preview.jsx
  6. 5 2
      package.json
  7. 12 0
      patches/svgo+1.3.2.patch
  8. 6 3
      src/App.tsx
  9. 7 5
      src/MainLayout.tsx
  10. 38 14
      src/api/client/cache.ts
  11. 2 1
      src/api/client/executors.ts
  12. 10 9
      src/api/client/index.ts
  13. 2 1
      src/api/client/resolvers.ts
  14. 2 1
      src/api/hooks/categories.ts
  15. 1 0
      src/api/hooks/channel.ts
  16. 1 2
      src/api/hooks/coverVideo.ts
  17. 1 1
      src/api/hooks/index.ts
  18. 1 0
      src/api/hooks/membership.ts
  19. 1 0
      src/api/hooks/queryNode.ts
  20. 2 1
      src/api/hooks/search.ts
  21. 3 13
      src/api/hooks/video.ts
  22. 4 0
      src/api/hooks/videosConnection.ts
  23. 3 20
      src/api/hooks/workers.ts
  24. 3 0
      src/api/queries/__generated__/baseTypes.generated.ts
  25. 3 2
      src/api/queries/__generated__/categories.generated.tsx
  26. 3 3
      src/api/queries/__generated__/channels.generated.tsx
  27. 3 3
      src/api/queries/__generated__/memberships.generated.tsx
  28. 3 2
      src/api/queries/__generated__/queryNode.generated.tsx
  29. 10 6
      src/api/queries/__generated__/search.generated.tsx
  30. 2 2
      src/api/queries/__generated__/shared.generated.tsx
  31. 4 4
      src/api/queries/__generated__/videos.generated.tsx
  32. 3 2
      src/api/queries/__generated__/workers.generated.tsx
  33. 2 2
      src/api/queries/search.graphql
  34. 2 1
      src/api/schemas/extendedQueryNode.graphql
  35. 0 0
      src/assets/bg.svg
  36. BIN
      src/assets/tile-example.png
  37. BIN
      src/assets/video-example.png
  38. 0 16
      src/assets/well-blue.svg
  39. 3 3
      src/components/BackgroundPattern.tsx
  40. 4 3
      src/components/ChannelGallery.tsx
  41. 3 2
      src/components/ChannelGrid.tsx
  42. 3 2
      src/components/ChannelLink/ChannelLink.style.ts
  43. 5 3
      src/components/ChannelLink/ChannelLink.tsx
  44. 1 0
      src/components/ChannelLink/index.ts
  45. 2 1
      src/components/ChannelPreview.tsx
  46. 3 1
      src/components/CoverVideo/CoverVideo.style.ts
  47. 10 8
      src/components/CoverVideo/CoverVideo.tsx
  48. 5 3
      src/components/Dialogs/ActionDialog/ActionDialog.stories.tsx
  49. 3 2
      src/components/Dialogs/ActionDialog/ActionDialog.style.ts
  50. 5 2
      src/components/Dialogs/ActionDialog/ActionDialog.tsx
  51. 5 3
      src/components/Dialogs/BaseDialog/BaseDialog.stories.tsx
  52. 17 2
      src/components/Dialogs/BaseDialog/BaseDialog.style.ts
  53. 22 21
      src/components/Dialogs/BaseDialog/BaseDialog.tsx
  54. 7 5
      src/components/Dialogs/ImageCropDialog/ImageCropDialog.stories.tsx
  55. 7 3
      src/components/Dialogs/ImageCropDialog/ImageCropDialog.style.ts
  56. 31 11
      src/components/Dialogs/ImageCropDialog/ImageCropDialog.tsx
  57. 18 6
      src/components/Dialogs/ImageCropDialog/cropper.ts
  58. 4 2
      src/components/Dialogs/MessageDialog/MessageDialog.stories.tsx
  59. 2 1
      src/components/Dialogs/MessageDialog/MessageDialog.style.ts
  60. 6 3
      src/components/Dialogs/MessageDialog/MessageDialog.tsx
  61. 4 2
      src/components/Dialogs/Multistepper/Multistepper.stories.tsx
  62. 4 2
      src/components/Dialogs/Multistepper/Multistepper.style.ts
  63. 6 3
      src/components/Dialogs/Multistepper/Multistepper.tsx
  64. 4 2
      src/components/Dialogs/TransactionDialog/TransactionDialog.stories.tsx
  65. 5 4
      src/components/Dialogs/TransactionDialog/TransactionDialog.style.ts
  66. 12 41
      src/components/Dialogs/TransactionDialog/TransactionDialog.tsx
  67. 2 2
      src/components/Dialogs/index.ts
  68. 1 2
      src/components/ErrorFallback.tsx
  69. 5 4
      src/components/InfiniteGrids/InfiniteChannelGrid.tsx
  70. 5 4
      src/components/InfiniteGrids/InfiniteVideoGrid.tsx
  71. 1 1
      src/components/InfiniteGrids/index.ts
  72. 3 3
      src/components/InfiniteGrids/useInfiniteGrid.ts
  73. 2 2
      src/components/InterruptedVideosGallery.tsx
  74. 2 1
      src/components/Link/Link.style.ts
  75. 1 0
      src/components/Link/Link.tsx
  76. 1 0
      src/components/Link/index.ts
  77. 9 5
      src/components/NoConnectionIndicator/NoConnectionIndicator.stories.tsx
  78. 3 2
      src/components/NoConnectionIndicator/NoConnectionIndicator.style.ts
  79. 4 2
      src/components/NoConnectionIndicator/NoConnectionIndicator.tsx
  80. 1 0
      src/components/NoConnectionIndicator/index.ts
  81. 1 0
      src/components/PlaceholderVideoGrid.tsx
  82. 5 4
      src/components/Sidenav/SidenavBase.style.ts
  83. 6 4
      src/components/Sidenav/SidenavBase.tsx
  84. 32 34
      src/components/Sidenav/StudioSidenav/StudioSidenav.tsx
  85. 4 2
      src/components/Sidenav/ViewerSidenav/FollowedChannels.style.ts
  86. 6 4
      src/components/Sidenav/ViewerSidenav/FollowedChannels.tsx
  87. 5 3
      src/components/Sidenav/ViewerSidenav/ViewerSidenav.tsx
  88. 5 4
      src/components/SignInSteps/AccountStep.style.ts
  89. 19 12
      src/components/SignInSteps/AccountStep.tsx
  90. 24 4
      src/components/SignInSteps/ExtensionStep.style.ts
  91. 42 9
      src/components/SignInSteps/ExtensionStep.tsx
  92. 10 7
      src/components/SignInSteps/SignInSteps.style.ts
  93. 5 2
      src/components/SignInSteps/SignInStepsStepper.tsx
  94. 2 1
      src/components/SignInSteps/TermsStep.style.tsx
  95. 5 3
      src/components/SignInSteps/TermsStep.tsx
  96. 4 3
      src/components/StudioEntrypoint.tsx
  97. 1 0
      src/components/TermsOfService.tsx
  98. 3 1
      src/components/Topbar/StudioTopbar/StudioTopbar.style.tsx
  99. 6 6
      src/components/Topbar/StudioTopbar/StudioTopbar.tsx
  100. 1 0
      src/components/Topbar/StudioTopbar/index.ts

+ 10 - 9
.env

@@ -1,15 +1,16 @@
 # This file is commited. Do not store secrets here
+
 REACT_APP_ENV=staging
-#REACT_APP_ENV=development
+REACT_APP_QUERY_NODE_URL=https://sumer-dev-2.joystream.app/query/server/graphql
+REACT_APP_QUERY_NODE_SUBSCRIPTION_URL=wss://sumer-dev-2.joystream.app/query/server/graphql
+REACT_APP_ORION_URL=https://orion-staging.joystream.app/graphql
+REACT_APP_NODE_URL=wss://sumer-dev-2.joystream.app/rpc
+REACT_APP_FAUCET_URL=https://sumer-dev-2.joystream.app/members/register
+
 #REACT_APP_ENV=production
-#REACT_APP_QUERY_NODE_URL=https://hydra-staging.joystream.app/server/graphql
 #REACT_APP_QUERY_NODE_URL=https://hydra.joystream.org/graphql
-REACT_APP_ORION_URL=https://orion-staging.joystream.app/graphql
+#REACT_APP_QUERY_NODE_SUBSCRIPTION_URL=wss://hydra.joystream.org/graphql
 #REACT_APP_ORION_URL=https://orion.joystream.org/graphql
-#REACT_APP_STORAGE_NODE_URL=https://staging-3.joystream.app/storage/asset/v0/
-#REACT_APP_STORAGE_NODE_URL=https://rome-rpc-endpoint.joystream.org/asset/v0/
+#REACT_APP_NODE_URL=wss://rome-rpc-endpoint.joystream.org:9944/
+#REACT_APP_FAUCET_URL=https://member-faucet.joystream.org/register
 
-REACT_APP_NODE_URL=wss://sumer-dev-2.joystream.app/rpc
-REACT_APP_FAUCET_URL=https://sumer-dev-2.joystream.app/members/register
-REACT_APP_QUERY_NODE_URL=https://sumer-dev-2.joystream.app/query/server/graphql
-REACT_APP_QUERY_NODE_SUBSCRIPTION_URL=wss://sumer-dev-2.joystream.app/query/server/graphql

+ 3 - 0
.gitignore

@@ -72,3 +72,6 @@ yarn-error.log
 
 # vscode/extensions
 .history
+
+# JetBrains
+.idea

+ 3 - 0
.prettierrc.js

@@ -1,4 +1,7 @@
 module.exports = {
   ...require('@joystream/prettier-config'),
   printWidth: 120,
+  importOrder: ['^@/(.*)$', '^./', '^[./]'],
+  experimentalBabelParserPluginsList: ['jsx', 'typescript'],
+  importOrderSeparation: true,
 }

+ 1 - 0
.storybook/manager.js

@@ -1,4 +1,5 @@
 import addons from '@storybook/addons'
+
 import joystreamTheme from './theme'
 
 addons.setConfig({

+ 6 - 2
.storybook/preview.jsx

@@ -1,8 +1,9 @@
-import React, { useRef } from 'react'
 import { css } from '@emotion/react'
-import { GlobalStyle } from '../src/shared/components'
+import React, { useRef } from 'react'
 import useResizeObserver from 'use-resize-observer'
 
+import { GlobalStyle } from '../src/shared/components'
+
 const wrapperStyle = css`
   padding: 10px;
   height: calc(100vh - 20px);
@@ -50,4 +51,7 @@ export const parameters = {
       },
     ],
   },
+  options: {
+    storySort: (a, b) => (a[1].kind === b[1].kind ? 0 : a[1].id.localeCompare(b[1].id, undefined, { numeric: true })),
+  },
 }

+ 5 - 2
package.json

@@ -29,7 +29,9 @@
     "mocking:channels": "node scripts/mocking/generateChannels.js",
     "codegen:graphql": "graphql-codegen --config codegen.config.yml",
     "codegen:graphql-watch": "yarn codegen:graphql --watch",
-    "codegen:icons": "svgr --config-file svgr-icons.config.js -d src/shared/icons src/shared/icons/svgs",
+    "codegen:icons": "svgr --config-file svgr.config.js -d src/shared/icons src/shared/icons/svgs",
+    "codegen:illustrations": "svgr --config-file svgr.config.js -d src/shared/illustrations src/shared/illustrations/svgs",
+    "codegen:svgs": "yarn codegen:illustrations && yarn codegen:icons",
     "postinstall": "patch-package"
   },
   "lint-staged": {
@@ -131,7 +133,8 @@
     "@storybook/preset-create-react-app": "^3.1.5",
     "@storybook/react": "^6.1.16",
     "@storybook/theming": "^6.1.16",
-    "@svgr/cli": "^5.5.0"
+    "@svgr/cli": "^5.5.0",
+    "@trivago/prettier-plugin-sort-imports": "^2.0.2"
   },
   "browserslist": {
     "production": [

+ 12 - 0
patches/svgo+1.3.2.patch

@@ -0,0 +1,12 @@
+diff --git a/node_modules/svgo/plugins/_collections.js b/node_modules/svgo/plugins/_collections.js
+index 8179f30..d3016ed 100644
+--- a/node_modules/svgo/plugins/_collections.js
++++ b/node_modules/svgo/plugins/_collections.js
+@@ -1519,6 +1519,7 @@ exports.elems = {
+             'x',
+             'y',
+             'width',
++            'mask-type',
+             'height',
+             'maskUnits',
+             'maskContentUnits'

+ 6 - 3
src/App.tsx

@@ -1,8 +1,9 @@
-import React from 'react'
 import { ApolloProvider } from '@apollo/client'
+import React from 'react'
 
 import { createApolloClient } from '@/api'
-import { ConnectionStatusProvider, OverlayManagerProvider, SnackbarProvider } from '@/hooks'
+import { ConnectionStatusProvider, OverlayManagerProvider, SnackbarProvider, StorageProvidersProvider } from '@/hooks'
+
 import MainLayout from './MainLayout'
 
 export default function App() {
@@ -15,7 +16,9 @@ export default function App() {
       <SnackbarProvider>
         <ConnectionStatusProvider>
           <OverlayManagerProvider>
-            <MainLayout />
+            <StorageProvidersProvider>
+              <MainLayout />
+            </StorageProvidersProvider>
           </OverlayManagerProvider>
         </ConnectionStatusProvider>
       </SnackbarProvider>

+ 7 - 5
src/MainLayout.tsx

@@ -1,13 +1,15 @@
+import loadable from '@loadable/component'
 import React from 'react'
 import { BrowserRouter, Routes, Route } from 'react-router-dom'
-import loadable from '@loadable/component'
-import { GlobalStyle } from '@/shared/components'
+
+import { TopbarBase, StudioLoading } from '@/components'
 import { BASE_PATHS } from '@/config/routes'
-import { ViewerLayout } from './views/viewer'
+import { GlobalStyle } from '@/shared/components'
+import { routingTransitions } from '@/styles/routingTransitions'
+
 import { LegalLayout } from './views/legal'
 import { PlaygroundLayout } from './views/playground'
-import { TopbarBase, StudioLoading } from '@/components'
-import { routingTransitions } from '@/styles/routingTransitions'
+import { ViewerLayout } from './views/viewer'
 
 const LoadableStudioLayout = loadable(() => import('./views/studio/StudioLayout'), {
   fallback: (

+ 38 - 14
src/api/client/cache.ts

@@ -1,19 +1,25 @@
 import { InMemoryCache } from '@apollo/client'
-import { offsetLimitPagination, Reference, relayStylePagination, StoreObject } from '@apollo/client/utilities'
+import { ReadFieldFunction } from '@apollo/client/cache/core/types/common'
+import { FieldPolicy, FieldReadFunction } from '@apollo/client/cache/inmemory/policies'
+import { offsetLimitPagination, relayStylePagination } from '@apollo/client/utilities'
 import { parseISO } from 'date-fns'
+
 import {
   AllChannelFieldsFragment,
   AssetAvailability,
+  GetVideosConnectionQueryVariables,
   GetVideosQueryVariables,
   Query,
+  VideoConnection,
   VideoFieldsFragment,
+  VideoOrderByInput,
 } from '../queries'
-import { FieldPolicy, FieldReadFunction } from '@apollo/client/cache/inmemory/policies'
 
 const getVideoKeyArgs = (args: Record<string, GetVideosQueryVariables['where']> | null) => {
   // make sure queries asking for a specific category are separated in cache
   const channelId = args?.where?.channelId_eq || ''
   const categoryId = args?.where?.categoryId_eq || ''
+  const idEq = args?.where?.id_eq || ''
   const isPublic = args?.where?.isPublic_eq ?? ''
   const channelIdIn = args?.where?.channelId_in ? JSON.stringify(args.where.channelId_in) : ''
   const createdAtGte = args?.where?.createdAt_gte ? JSON.stringify(args.where.createdAt_gte) : ''
@@ -23,7 +29,7 @@ const getVideoKeyArgs = (args: Record<string, GetVideosQueryVariables['where']>
     return `${createdAtGte}:${channelIdIn}`
   }
 
-  return `${channelId}:${categoryId}:${channelIdIn}:${createdAtGte}:${isPublic}`
+  return `${channelId}:${categoryId}:${channelIdIn}:${createdAtGte}:${isPublic}:${idEq}`
 }
 
 const createDateHandler = () => ({
@@ -80,21 +86,39 @@ type CachePolicyFields<T extends string> = Partial<Record<T, FieldPolicy | Field
 
 const queryCacheFields: CachePolicyFields<keyof Query> = {
   channelsConnection: relayStylePagination(),
-  videosConnection: relayStylePagination(getVideoKeyArgs),
+  videosConnection: {
+    ...relayStylePagination(getVideoKeyArgs),
+    read(
+      existing: VideoConnection,
+      { args, readField }: { args: GetVideosConnectionQueryVariables | null; readField: ReadFieldFunction }
+    ) {
+      const isPublic = args?.where?.isPublic_eq
+      const filteredEdges =
+        existing?.edges.filter((edge) => readField('isPublic', edge.node) === isPublic || isPublic === undefined) ?? []
+
+      const sortingASC = args?.orderBy === VideoOrderByInput.CreatedAtAsc
+      const preSortedDESC = (filteredEdges || [])
+        .slice()
+        .sort(
+          (a, b) =>
+            (readField('createdAt', b.node) as Date).getTime() - (readField('createdAt', a.node) as Date).getTime()
+        )
+      const sortedEdges = sortingASC ? preSortedDESC.reverse() : preSortedDESC
+
+      return (
+        existing && {
+          ...existing,
+          edges: sortedEdges,
+        }
+      )
+    },
+  },
   videos: {
     ...offsetLimitPagination(getVideoKeyArgs),
     read(existing, opts) {
-      const isPublic = opts.args?.where.isPublic_eq
-
-      const filteredExistingVideos = existing?.filter(
-        (v: StoreObject | Reference) => opts.readField('isPublic', v) === isPublic || isPublic === undefined
-      )
-      // Default to returning the entire cached list,
-      // if offset and limit are not provided.
       const offset = opts.args?.offset ?? 0
-      const limit = opts.args?.limit ?? filteredExistingVideos?.length
-
-      return filteredExistingVideos?.slice(offset, offset + limit)
+      const limit = opts.args?.limit ?? existing?.length
+      return existing?.slice(offset, offset + limit)
     },
   },
   channelByUniqueInput: (existing, { toReference, args }) => {

+ 2 - 1
src/api/client/executors.ts

@@ -1,7 +1,8 @@
-import { ORION_GRAPHQL_URL, QUERY_NODE_GRAPHQL_URL } from '@/config/urls'
 import { HttpLink } from '@apollo/client'
 import { linkToExecutor } from '@graphql-tools/links'
 
+import { ORION_GRAPHQL_URL, QUERY_NODE_GRAPHQL_URL } from '@/config/urls'
+
 const createExecutors = () => {
   const queryNodeLink = new HttpLink({ uri: QUERY_NODE_GRAPHQL_URL })
   const orionLink = new HttpLink({ uri: ORION_GRAPHQL_URL })

+ 10 - 9
src/api/client/index.ts

@@ -1,20 +1,21 @@
 import { ApolloClient, split } from '@apollo/client'
+import { SchemaLink } from '@apollo/client/link/schema'
 import { WebSocketLink } from '@apollo/client/link/ws'
-import { wrapSchema } from '@graphql-tools/wrap'
+import { getMainDefinition } from '@apollo/client/utilities'
+import { delegateToSchema } from '@graphql-tools/delegate'
+import { CreateProxyingResolverFn } from '@graphql-tools/delegate/types'
 import { mergeSchemas } from '@graphql-tools/merge'
+import { wrapSchema } from '@graphql-tools/wrap'
 import { buildASTSchema, GraphQLFieldResolver } from 'graphql'
-import { SchemaLink } from '@apollo/client/link/schema'
 
-import extendedQueryNodeSchema from '../schemas/extendedQueryNode.graphql'
-import orionSchema from '../schemas/orion.graphql'
+import { createExecutors } from '@/api/client/executors'
+import { QUERY_NODE_GRAPHQL_SUBSCRIPTION_URL } from '@/config/urls'
 
 import cache from './cache'
 import { queryNodeStitchingResolvers } from './resolvers'
-import { createExecutors } from '@/api/client/executors'
-import { delegateToSchema } from '@graphql-tools/delegate'
-import { CreateProxyingResolverFn } from '@graphql-tools/delegate/types'
-import { getMainDefinition } from '@apollo/client/utilities'
-import { QUERY_NODE_GRAPHQL_SUBSCRIPTION_URL } from '@/config/urls'
+
+import extendedQueryNodeSchema from '../schemas/extendedQueryNode.graphql'
+import orionSchema from '../schemas/orion.graphql'
 
 // we do this so that operationName is passed along with the queries
 // this is needed for our mocking backend to operate

+ 2 - 1
src/api/client/resolvers.ts

@@ -1,6 +1,7 @@
-import { GraphQLSchema } from 'graphql'
 import { delegateToSchema, Transform } from '@graphql-tools/delegate'
 import type { IResolvers, ISchemaLevelResolver } from '@graphql-tools/utils'
+import { GraphQLSchema } from 'graphql'
+
 import {
   TransformOrionViewsField,
   ORION_VIEWS_QUERY_NAME,

+ 2 - 1
src/api/hooks/categories.ts

@@ -1,6 +1,7 @@
-import { useGetVideoCategoriesQuery, GetVideoCategoriesQuery, GetVideoCategoriesQueryVariables } from '@/api/queries'
 import { QueryHookOptions } from '@apollo/client'
 
+import { useGetVideoCategoriesQuery, GetVideoCategoriesQuery, GetVideoCategoriesQueryVariables } from '@/api/queries'
+
 type Opts = QueryHookOptions<GetVideoCategoriesQuery>
 const useCategories = (variables?: GetVideoCategoriesQueryVariables, opts?: Opts) => {
   const { data, ...rest } = useGetVideoCategoriesQuery({ ...opts, variables })

+ 1 - 0
src/api/hooks/channel.ts

@@ -1,4 +1,5 @@
 import { MutationHookOptions, QueryHookOptions } from '@apollo/client'
+
 import {
   AssetAvailability,
   FollowChannelMutation,

+ 1 - 2
src/api/hooks/coverVideo.ts

@@ -1,7 +1,6 @@
 import { CoverVideo, AllChannelFieldsFragment } from '@/api/queries'
-import { MockVideo } from '@/mocking/data/mockVideos'
-
 import { mockCoverVideo } from '@/mocking/data/mockCoverVideo'
+import { MockVideo } from '@/mocking/data/mockVideos'
 
 type UseCoverVideo = () => {
   data: {

+ 1 - 1
src/api/hooks/index.ts

@@ -1,5 +1,5 @@
-import useCoverVideo from './coverVideo'
 import useCategories from './categories'
+import useCoverVideo from './coverVideo'
 import useSearch from './search'
 import useVideosConnection from './videosConnection'
 

+ 1 - 0
src/api/hooks/membership.ts

@@ -1,4 +1,5 @@
 import { QueryHookOptions } from '@apollo/client'
+
 import {
   GetMembershipQuery,
   useGetMembershipQuery,

+ 1 - 0
src/api/hooks/queryNode.ts

@@ -1,4 +1,5 @@
 import { SubscriptionHookOptions } from '@apollo/client'
+
 import {
   GetQueryNodeStateSubscription,
   GetQueryNodeStateSubscriptionVariables,

+ 2 - 1
src/api/hooks/search.ts

@@ -1,6 +1,7 @@
-import { useSearchQuery, SearchQueryVariables, SearchQuery } from '@/api/queries'
 import { QueryHookOptions } from '@apollo/client'
 
+import { useSearchQuery, SearchQueryVariables, SearchQuery } from '@/api/queries'
+
 type Opts = QueryHookOptions<SearchQuery>
 const useSearch = (variables: SearchQueryVariables, opts?: Opts) => {
   const { data, ...rest } = useSearchQuery({ ...opts, variables })

+ 3 - 13
src/api/hooks/video.ts

@@ -1,3 +1,5 @@
+import { QueryHookOptions, MutationHookOptions } from '@apollo/client'
+
 import {
   useGetVideoQuery,
   useAddVideoViewMutation,
@@ -6,9 +8,7 @@ import {
   GetVideosQuery,
   GetVideosQueryVariables,
   useGetVideosQuery,
-  useGetVideoCountQuery,
 } from '@/api/queries'
-import { QueryHookOptions, MutationHookOptions } from '@apollo/client'
 
 type VideoOpts = QueryHookOptions<GetVideoQuery>
 export const useVideo = (id: string, opts?: VideoOpts) => {
@@ -24,19 +24,9 @@ export const useVideo = (id: string, opts?: VideoOpts) => {
 
 type VideosOpts = QueryHookOptions<GetVideosQuery>
 export const useVideos = (variables?: GetVideosQueryVariables, opts?: VideosOpts) => {
-  const { data, loading: videosLoading, ...rest } = useGetVideosQuery({ ...opts, variables })
-  // Only way to get the video count for pagination as of now
-  const { data: connectionData, loading: countLoading, refetch: refetchCount } = useGetVideoCountQuery({
-    ...opts,
-    variables: {
-      where: variables?.where,
-    },
-  })
+  const { data, ...rest } = useGetVideosQuery({ ...opts, variables })
   return {
     videos: data?.videos,
-    loading: videosLoading || countLoading,
-    totalCount: connectionData?.videosConnection.totalCount,
-    refetchCount,
     ...rest,
   }
 }

+ 4 - 0
src/api/hooks/videosConnection.ts

@@ -1,4 +1,5 @@
 import { QueryHookOptions } from '@apollo/client'
+
 import { GetVideosConnectionQuery, GetVideosConnectionQueryVariables, useGetVideosConnectionQuery } from '@/api/queries'
 
 type Opts = QueryHookOptions<GetVideosConnectionQuery>
@@ -7,6 +8,9 @@ const useVideosConnection = (variables?: GetVideosConnectionQueryVariables, opts
 
   return {
     videosConnection: data?.videosConnection,
+    edges: data?.videosConnection.edges,
+    totalCount: data?.videosConnection.totalCount,
+    pageInfo: data?.videosConnection.pageInfo,
     ...rest,
   }
 }

+ 3 - 20
src/api/hooks/workers.ts

@@ -1,5 +1,6 @@
-import { getRandomIntInclusive } from '@/utils/number'
 import { QueryHookOptions } from '@apollo/client'
+
+import { WorkerType } from '@/api/queries'
 import {
   GetWorkerQuery,
   GetWorkersQuery,
@@ -7,8 +8,6 @@ import {
   useGetWorkerQuery,
   useGetWorkersQuery,
 } from '@/api/queries/__generated__/workers.generated'
-import { WorkerType } from '@/api/queries'
-import { useCallback } from 'react'
 
 type WorkerOpts = QueryHookOptions<GetWorkerQuery>
 export const useWorker = (id: string, opts?: WorkerOpts) => {
@@ -23,7 +22,7 @@ export const useWorker = (id: string, opts?: WorkerOpts) => {
 }
 
 type WorkersOpts = QueryHookOptions<GetWorkersQuery>
-export const useStorageProviders = (variables: GetWorkersQueryVariables, opts?: WorkersOpts) => {
+export const useStorageWorkers = (variables: GetWorkersQueryVariables, opts?: WorkersOpts) => {
   const { data, loading, ...rest } = useGetWorkersQuery({
     ...opts,
     variables: {
@@ -42,19 +41,3 @@ export const useStorageProviders = (variables: GetWorkersQueryVariables, opts?:
     ...rest,
   }
 }
-
-export const useRandomStorageProviderUrl = () => {
-  const { storageProviders, loading } = useStorageProviders({ limit: 100 }, { fetchPolicy: 'network-only' })
-
-  const getRandomStorageProviderUrl = useCallback(() => {
-    if (storageProviders?.length && !loading) {
-      const randomStorageIdx = getRandomIntInclusive(0, storageProviders.length - 1)
-      return storageProviders[randomStorageIdx].metadata
-    } else if (!loading) {
-      console.error('No active storage provider available')
-    }
-    return null
-  }, [loading, storageProviders])
-
-  return { getRandomStorageProviderUrl }
-}

+ 3 - 0
src/api/queries/__generated__/baseTypes.generated.ts

@@ -200,6 +200,7 @@ export type VideoWhereInput = {
   isPublic_eq?: Maybe<Scalars['Boolean']>
   isCensored_eq?: Maybe<Scalars['Boolean']>
   id_in?: Maybe<Array<Scalars['ID']>>
+  id_eq?: Maybe<Scalars['ID']>
 }
 
 export type VideoWhereUniqueInput = {
@@ -343,6 +344,8 @@ export type QueryMembershipsArgs = {
 export type QuerySearchArgs = {
   limit?: Maybe<Scalars['Int']>
   text: Scalars['String']
+  whereVideo?: Maybe<VideoWhereInput>
+  whereChannel?: Maybe<ChannelWhereInput>
 }
 
 export type QueryVideoByUniqueInputArgs = {

+ 3 - 2
src/api/queries/__generated__/categories.generated.tsx

@@ -1,7 +1,8 @@
-import * as Types from './baseTypes.generated'
-
 import { gql } from '@apollo/client'
 import * as Apollo from '@apollo/client'
+
+import * as Types from './baseTypes.generated'
+
 export type VideoCategoryFieldsFragment = { __typename?: 'VideoCategory'; id: string; name?: Types.Maybe<string> }
 
 export type GetVideoCategoriesQueryVariables = Types.Exact<{ [key: string]: never }>

+ 3 - 3
src/api/queries/__generated__/channels.generated.tsx

@@ -1,9 +1,9 @@
-import * as Types from './baseTypes.generated'
+import { gql } from '@apollo/client'
+import * as Apollo from '@apollo/client'
 
+import * as Types from './baseTypes.generated'
 import { DataObjectFieldsFragment, DataObjectFieldsFragmentDoc } from './shared.generated'
-import { gql } from '@apollo/client'
 
-import * as Apollo from '@apollo/client'
 export type BasicChannelFieldsFragment = {
   __typename?: 'Channel'
   id: string

+ 3 - 3
src/api/queries/__generated__/memberships.generated.tsx

@@ -1,9 +1,9 @@
-import * as Types from './baseTypes.generated'
+import { gql } from '@apollo/client'
+import * as Apollo from '@apollo/client'
 
+import * as Types from './baseTypes.generated'
 import { BasicChannelFieldsFragment, BasicChannelFieldsFragmentDoc } from './channels.generated'
-import { gql } from '@apollo/client'
 
-import * as Apollo from '@apollo/client'
 export type BasicMembershipFieldsFragment = {
   __typename?: 'Membership'
   id: string

+ 3 - 2
src/api/queries/__generated__/queryNode.generated.tsx

@@ -1,7 +1,8 @@
-import * as Types from './baseTypes.generated'
-
 import { gql } from '@apollo/client'
 import * as Apollo from '@apollo/client'
+
+import * as Types from './baseTypes.generated'
+
 export type GetQueryNodeStateSubscriptionVariables = Types.Exact<{ [key: string]: never }>
 
 export type GetQueryNodeStateSubscription = {

+ 10 - 6
src/api/queries/__generated__/search.generated.tsx

@@ -1,12 +1,14 @@
-import * as Types from './baseTypes.generated'
+import { gql } from '@apollo/client'
+import * as Apollo from '@apollo/client'
 
-import { VideoFieldsFragment, VideoFieldsFragmentDoc } from './videos.generated'
+import * as Types from './baseTypes.generated'
 import { BasicChannelFieldsFragment, BasicChannelFieldsFragmentDoc } from './channels.generated'
-import { gql } from '@apollo/client'
+import { VideoFieldsFragment, VideoFieldsFragmentDoc } from './videos.generated'
 
-import * as Apollo from '@apollo/client'
 export type SearchQueryVariables = Types.Exact<{
   text: Types.Scalars['String']
+  whereVideo?: Types.Maybe<Types.VideoWhereInput>
+  whereChannel?: Types.Maybe<Types.ChannelWhereInput>
 }>
 
 export type SearchQuery = {
@@ -18,8 +20,8 @@ export type SearchQuery = {
 }
 
 export const SearchDocument = gql`
-  query Search($text: String!) {
-    search(text: $text) {
+  query Search($text: String!, $whereVideo: VideoWhereInput, $whereChannel: ChannelWhereInput) {
+    search(text: $text, whereVideo: $whereVideo, whereChannel: $whereChannel) {
       item {
         ... on Video {
           ...VideoFields
@@ -47,6 +49,8 @@ export const SearchDocument = gql`
  * const { data, loading, error } = useSearchQuery({
  *   variables: {
  *      text: // value for 'text'
+ *      whereVideo: // value for 'whereVideo'
+ *      whereChannel: // value for 'whereChannel'
  *   },
  * });
  */

+ 2 - 2
src/api/queries/__generated__/shared.generated.tsx

@@ -1,7 +1,7 @@
-import * as Types from './baseTypes.generated'
+import { gql } from '@apollo/client'
 
+import * as Types from './baseTypes.generated'
 import { BasicWorkerFieldsFragment, BasicWorkerFieldsFragmentDoc } from './workers.generated'
-import { gql } from '@apollo/client'
 
 export type DataObjectFieldsFragment = {
   __typename?: 'DataObject'

+ 4 - 4
src/api/queries/__generated__/videos.generated.tsx

@@ -1,10 +1,10 @@
-import * as Types from './baseTypes.generated'
+import { gql } from '@apollo/client'
+import * as Apollo from '@apollo/client'
 
-import { DataObjectFieldsFragment, DataObjectFieldsFragmentDoc } from './shared.generated'
+import * as Types from './baseTypes.generated'
 import { BasicChannelFieldsFragment, BasicChannelFieldsFragmentDoc } from './channels.generated'
-import { gql } from '@apollo/client'
+import { DataObjectFieldsFragment, DataObjectFieldsFragmentDoc } from './shared.generated'
 
-import * as Apollo from '@apollo/client'
 export type VideoMediaMetadataFieldsFragment = {
   __typename?: 'VideoMediaMetadata'
   id: string

+ 3 - 2
src/api/queries/__generated__/workers.generated.tsx

@@ -1,7 +1,8 @@
-import * as Types from './baseTypes.generated'
-
 import { gql } from '@apollo/client'
 import * as Apollo from '@apollo/client'
+
+import * as Types from './baseTypes.generated'
+
 export type BasicWorkerFieldsFragment = {
   __typename?: 'Worker'
   id: string

+ 2 - 2
src/api/queries/search.graphql

@@ -1,5 +1,5 @@
-query Search($text: String!) {
-  search(text: $text) {
+query Search($text: String!, $whereVideo: VideoWhereInput, $whereChannel: ChannelWhereInput) {
+  search(text: $text, whereVideo: $whereVideo, whereChannel: $whereChannel) {
     item {
       ... on Video {
         ...VideoFields

+ 2 - 1
src/api/schemas/extendedQueryNode.graphql

@@ -201,6 +201,7 @@ input VideoWhereInput {
   isPublic_eq: Boolean
   isCensored_eq: Boolean
   id_in: [ID!]
+  id_eq: ID
 }
 
 input VideoWhereUniqueInput {
@@ -305,7 +306,7 @@ type Query {
   coverVideo: CoverVideo!
 
   # Free text search across videos and channels
-  search(limit: Int, text: String!): [SearchFTSOutput!]!
+  search(limit: Int, text: String!, whereVideo: VideoWhereInput, whereChannel: ChannelWhereInput): [SearchFTSOutput!]!
 }
 
 type Subscription {

Plik diff jest za duży
+ 0 - 0
src/assets/bg.svg


BIN
src/assets/tile-example.png


BIN
src/assets/video-example.png


Plik diff jest za duży
+ 0 - 16
src/assets/well-blue.svg


+ 3 - 3
src/components/BackgroundPattern.tsx

@@ -1,7 +1,7 @@
-import React from 'react'
 import styled from '@emotion/styled'
+import React from 'react'
 
-import { ReactComponent as BackgroundPatternSVG } from '@/assets/bg-pattern.svg'
+import { SvgBgPattern } from '@/shared/illustrations'
 import { zIndex, transitions, media } from '@/shared/theme'
 
 const PATTERN_OFFSET = {
@@ -21,7 +21,7 @@ const StyledBackgroundPatternContainer = styled.div`
   }
 `
 
-const StyledBackgroundPattern = styled(BackgroundPatternSVG)`
+const StyledBackgroundPattern = styled(SvgBgPattern)`
   display: none;
   position: absolute;
   transform: scale(1.3);

+ 4 - 3
src/components/ChannelGallery.tsx

@@ -1,10 +1,11 @@
-import React from 'react'
 import styled from '@emotion/styled'
+import React from 'react'
 
+import { BasicChannelFieldsFragment } from '@/api/queries'
 import { Gallery } from '@/shared/components'
-import ChannelPreview from './ChannelPreview'
 import { sizes } from '@/shared/theme'
-import { BasicChannelFieldsFragment } from '@/api/queries'
+
+import ChannelPreview from './ChannelPreview'
 
 type ChannelGalleryProps = {
   title?: string

+ 3 - 2
src/components/ChannelGrid.tsx

@@ -1,9 +1,10 @@
-import React from 'react'
 import styled from '@emotion/styled'
+import React from 'react'
 
+import { BasicChannelFieldsFragment } from '@/api/queries'
 import { Grid } from '@/shared/components'
+
 import ChannelPreview from './ChannelPreview'
-import { BasicChannelFieldsFragment } from '@/api/queries'
 
 const StyledChannelPreview = styled(ChannelPreview)`
   margin: 0 auto;

+ 3 - 2
src/components/ChannelLink/ChannelLink.style.ts

@@ -1,8 +1,9 @@
 import styled from '@emotion/styled'
-import { colors, sizes, typography } from '@/shared/theme'
-import { Placeholder } from '@/shared/components'
 import { Link } from 'react-router-dom'
 
+import { Placeholder } from '@/shared/components'
+import { colors, sizes, typography } from '@/shared/theme'
+
 type ContainerProps = {
   disabled?: boolean
 }

+ 5 - 3
src/components/ChannelLink/ChannelLink.tsx

@@ -1,10 +1,12 @@
 import React from 'react'
-import Avatar, { AvatarSize } from '@/shared/components/Avatar'
-import { absoluteRoutes } from '@/config/routes'
-import { Container, Handle, HandlePlaceholder } from './ChannelLink.style'
+
 import { useBasicChannel } from '@/api/hooks'
 import { BasicChannelFieldsFragment } from '@/api/queries'
+import { absoluteRoutes } from '@/config/routes'
 import { useAsset } from '@/hooks'
+import Avatar, { AvatarSize } from '@/shared/components/Avatar'
+
+import { Container, Handle, HandlePlaceholder } from './ChannelLink.style'
 
 type ChannelLinkProps = {
   id?: string

+ 1 - 0
src/components/ChannelLink/index.ts

@@ -1,2 +1,3 @@
 import ChannelLink from './ChannelLink'
+
 export default ChannelLink

+ 2 - 1
src/components/ChannelPreview.tsx

@@ -1,9 +1,10 @@
 import React from 'react'
-import { ChannelPreviewBase } from '@/shared/components'
+
 import { useChannel } from '@/api/hooks'
 import { useChannelVideoCount } from '@/api/hooks/channel'
 import { absoluteRoutes } from '@/config/routes'
 import { useAsset } from '@/hooks'
+import { ChannelPreviewBase } from '@/shared/components'
 
 type ChannelPreviewProps = {
   id?: string

+ 3 - 1
src/components/CoverVideo/CoverVideo.style.ts

@@ -1,8 +1,10 @@
+import { css, keyframes } from '@emotion/react'
 import styled from '@emotion/styled'
 import { darken, fluidRange } from 'polished'
+
 import { Button, IconButton, Placeholder, Text } from '@/shared/components'
 import { breakpoints, colors, sizes, media } from '@/shared/theme'
-import { css, keyframes } from '@emotion/react'
+
 import ChannelLink from '../ChannelLink'
 
 const CONTENT_OVERLAP_MAP = {

+ 10 - 8
src/components/CoverVideo/CoverVideo.tsx

@@ -1,4 +1,14 @@
 import React, { useState } from 'react'
+import { Link } from 'react-router-dom'
+import { CSSTransition } from 'react-transition-group'
+
+import { useCoverVideo } from '@/api/hooks'
+import { absoluteRoutes } from '@/config/routes'
+import { useAsset } from '@/hooks'
+import { Placeholder, VideoPlayer } from '@/shared/components'
+import { SvgPlayerPause, SvgPlayerPlay, SvgPlayerSoundOff, SvgPlayerSoundOn } from '@/shared/icons'
+import { transitions } from '@/shared/theme'
+
 import {
   Container,
   HorizontalGradientOverlay,
@@ -17,14 +27,6 @@ import {
   ControlsContainer,
   ButtonsContainer,
 } from './CoverVideo.style'
-import { CSSTransition } from 'react-transition-group'
-import { absoluteRoutes } from '@/config/routes'
-import { Placeholder, VideoPlayer } from '@/shared/components'
-import { Link } from 'react-router-dom'
-import { transitions } from '@/shared/theme'
-import { useCoverVideo } from '@/api/hooks'
-import { SvgPlayerPause, SvgPlayerPlay, SvgPlayerSoundOff, SvgPlayerSoundOn } from '@/shared/icons'
-import { useAsset } from '@/hooks'
 
 const VIDEO_PLAYBACK_DELAY = 1250
 

+ 5 - 3
src/components/Dialogs/ActionDialog/ActionDialog.stories.tsx

@@ -1,8 +1,10 @@
-import React, { useState } from 'react'
-import ActionDialog, { ActionDialogProps } from './ActionDialog'
 import { Story, Meta } from '@storybook/react'
-import { Button } from '@/shared/components'
+import React, { useState } from 'react'
+
 import { OverlayManagerProvider } from '@/hooks/useOverlayManager'
+import { Button } from '@/shared/components'
+
+import ActionDialog, { ActionDialogProps } from './ActionDialog'
 
 export default {
   title: 'General/ActionDialog',

+ 3 - 2
src/components/Dialogs/ActionDialog/ActionDialog.style.ts

@@ -1,7 +1,8 @@
-import styled from '@emotion/styled'
 import { css } from '@emotion/react'
-import { media, sizes, colors, typography } from '@/shared/theme'
+import styled from '@emotion/styled'
+
 import { Button } from '@/shared/components'
+import { media, sizes, colors, typography } from '@/shared/theme'
 
 type ButtonProps = {
   error?: boolean

+ 5 - 2
src/components/Dialogs/ActionDialog/ActionDialog.tsx

@@ -1,12 +1,15 @@
 import React from 'react'
-import BaseDialog, { BaseDialogProps } from '../BaseDialog'
+
+import { Button } from '@/shared/components'
+
 import {
   ActionsContainer,
   ButtonsContainer,
   AdditionalActionsContainer,
   StyledPrimaryButton,
 } from './ActionDialog.style'
-import { Button } from '@/shared/components'
+
+import BaseDialog, { BaseDialogProps } from '../BaseDialog'
 
 export type ActionDialogProps = {
   additionalActionsNode?: React.ReactNode

+ 5 - 3
src/components/Dialogs/BaseDialog/BaseDialog.stories.tsx

@@ -1,8 +1,10 @@
-import React, { useState } from 'react'
-import Dialog, { BaseDialogProps } from './BaseDialog'
 import { Story, Meta } from '@storybook/react'
-import { Button } from '@/shared/components'
+import React, { useState } from 'react'
+
 import { OverlayManagerProvider } from '@/hooks/useOverlayManager'
+import { Button } from '@/shared/components'
+
+import Dialog, { BaseDialogProps } from './BaseDialog'
 
 export default {
   title: 'General/BaseDialog',

+ 17 - 2
src/components/Dialogs/BaseDialog/BaseDialog.style.ts

@@ -1,14 +1,29 @@
 import styled from '@emotion/styled'
+
 import { IconButton } from '@/shared/components'
-import { colors, sizes, media } from '@/shared/theme'
+import { colors, sizes, media, zIndex } from '@/shared/theme'
+
+export const DialogBackDrop = styled.div`
+  position: fixed;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  z-index: ${zIndex.globalOverlay};
+  background-color: rgba(0, 0, 0, 0.4);
+  transition: opacity 150ms cubic-bezier(0.25, 0.01, 0.25, 1);
+`
 
 export const StyledContainer = styled.div`
   --dialog-padding: ${sizes(4)};
   ${media.small} {
     --dialog-padding: ${sizes(6)};
   }
+  z-index: ${zIndex.globalOverlay};
 
-  position: relative;
+  position: fixed;
+  left: 50%;
+  transform: translateX(-50%);
   width: 90%;
   max-width: 440px;
   min-height: 150px;

+ 22 - 21
src/components/Dialogs/BaseDialog/BaseDialog.tsx

@@ -1,10 +1,12 @@
 import React, { useEffect } from 'react'
-import { Portal } from '@/components'
-import { useOverlayManager } from '@/hooks/useOverlayManager'
 import { CSSTransition } from 'react-transition-group'
-import { StyledContainer, StyledExitButton } from './BaseDialog.style'
-import { transitions } from '@/shared/theme'
+
+import Portal from '@/components/Portal'
+import { useOverlayManager } from '@/hooks'
 import { SvgGlyphClose } from '@/shared/icons'
+import { transitions } from '@/shared/theme'
+
+import { DialogBackDrop, StyledContainer, StyledExitButton } from './BaseDialog.style'
 
 export type BaseDialogProps = {
   showDialog?: boolean
@@ -14,29 +16,28 @@ export type BaseDialogProps = {
 }
 
 const BaseDialog: React.FC<BaseDialogProps> = ({ children, showDialog, exitButton = true, onExitClick, className }) => {
-  const {
-    overlayContainerRef,
-    lockScroll,
-    unlockScroll,
-    openOverlayContainer,
-    closeOverlayContainer,
-  } = useOverlayManager()
+  const { dialogContainerRef, incrementOverlaysOpenCount, decrementOverlaysOpenCount } = useOverlayManager()
 
   useEffect(() => {
-    if (!showDialog) {
-      return
-    }
-    lockScroll()
-    openOverlayContainer()
     return () => {
-      unlockScroll()
-      closeOverlayContainer()
+      decrementOverlaysOpenCount()
     }
-  }, [showDialog, lockScroll, unlockScroll, openOverlayContainer, closeOverlayContainer])
+  }, [decrementOverlaysOpenCount])
 
   return (
-    <Portal containerRef={overlayContainerRef}>
-      <CSSTransition in={showDialog} timeout={250} classNames={transitions.names.dialog} mountOnEnter unmountOnExit>
+    <Portal containerRef={dialogContainerRef}>
+      <CSSTransition in={showDialog} timeout={200} classNames={transitions.names.fade} mountOnEnter unmountOnExit>
+        <DialogBackDrop />
+      </CSSTransition>
+      <CSSTransition
+        in={showDialog}
+        timeout={200}
+        classNames={transitions.names.dialog}
+        mountOnEnter
+        unmountOnExit
+        onEnter={incrementOverlaysOpenCount}
+        onExited={decrementOverlaysOpenCount}
+      >
         <StyledContainer className={className}>
           {exitButton && (
             <StyledExitButton aria-label="close dialog" onClick={onExitClick} variant="tertiary">

+ 7 - 5
src/components/Dialogs/ImageCropDialog/ImageCropDialog.stories.tsx

@@ -1,11 +1,13 @@
-import React, { useState, useRef } from 'react'
+import { css } from '@emotion/react'
+import styled from '@emotion/styled/'
 import { Story, Meta } from '@storybook/react'
+import React, { useState, useRef } from 'react'
+
+import { OverlayManagerProvider } from '@/hooks'
+import { Avatar, Placeholder } from '@/shared/components'
 import { ImageCropData, AssetDimensions } from '@/types/cropper'
+
 import ImageCropDialog, { ImageCropDialogImperativeHandle, ImageCropDialogProps } from './ImageCropDialog'
-import { Avatar, Placeholder } from '@/shared/components'
-import { OverlayManagerProvider } from '@/hooks'
-import { css } from '@emotion/react'
-import styled from '@emotion/styled/'
 
 export default {
   title: 'General/ImageCropDialog',

+ 7 - 3
src/components/Dialogs/ImageCropDialog/ImageCropDialog.style.ts

@@ -1,8 +1,10 @@
+import { css } from '@emotion/react'
+import styled from '@emotion/styled'
+
 import { Placeholder, Text } from '@/shared/components'
 import Slider from '@/shared/components/Slider'
 import { colors, sizes } from '@/shared/theme'
-import { css } from '@emotion/react'
-import styled from '@emotion/styled'
+
 import ActionDialog from '../ActionDialog'
 
 export const StyledActionDialog = styled(ActionDialog)`
@@ -53,7 +55,7 @@ export const CropPlaceholder = styled(Placeholder)`
   ${cropAreaSizeCss};
 `
 
-export const CropContainer = styled.div<{ rounded?: boolean }>`
+export const CropContainer = styled.div<{ rounded?: boolean; disabled?: boolean }>`
   ${cropAreaSizeCss};
 
   ${({ rounded }) => rounded && roundedCropperCss};
@@ -76,6 +78,8 @@ export const CropContainer = styled.div<{ rounded?: boolean }>`
   .cropper-modal {
     background-color: ${colors.transparentBlack[54]};
   }
+
+  pointer-events: ${({ disabled }) => disabled && 'none'};
 `
 
 export const StyledImage = styled.img`

+ 31 - 11
src/components/Dialogs/ImageCropDialog/ImageCropDialog.tsx

@@ -1,8 +1,10 @@
+import React, { forwardRef, useCallback, useImperativeHandle, useRef, useState } from 'react'
+
 import { IconButton } from '@/shared/components'
+import { SvgGlyphPan, SvgGlyphZoomIn, SvgGlyphZoomOut } from '@/shared/icons'
 import { ImageCropData, AssetDimensions } from '@/types/cropper'
-import React, { forwardRef, useCallback, useImperativeHandle, useRef, useState } from 'react'
-import { ActionDialogProps } from '../ActionDialog'
-import { CropperImageType, useCropper } from './cropper'
+import { validateImage } from '@/utils/image'
+
 import {
   AlignInfo,
   AlignInfoContainer,
@@ -15,8 +17,9 @@ import {
   StyledSlider,
   ZoomControl,
 } from './ImageCropDialog.style'
-import { SvgGlyphPan, SvgGlyphZoomIn, SvgGlyphZoomOut } from '@/shared/icons'
-import { validateImage } from '@/utils/image'
+import { CropperImageType, useCropper } from './cropper'
+
+import { ActionDialogProps } from '../ActionDialog'
 
 export type ImageCropDialogProps = {
   imageType: CropperImageType
@@ -30,7 +33,7 @@ export type ImageCropDialogProps = {
 } & Pick<ActionDialogProps, 'onExitClick'>
 
 export type ImageCropDialogImperativeHandle = {
-  open: (file?: File | Blob) => void
+  open: (file?: File | Blob, cropData?: ImageCropData) => void
 }
 
 const ImageCropDialogComponent: React.ForwardRefRenderFunction<
@@ -41,19 +44,27 @@ const ImageCropDialogComponent: React.ForwardRefRenderFunction<
   const inputRef = useRef<HTMLInputElement>(null)
   const [imageEl, setImageEl] = useState<HTMLImageElement | null>(null)
   const [editedImageHref, setEditedImageHref] = useState<string | null>(null)
-  const { currentZoom, zoomRange, zoomStep, handleZoomChange, cropImage } = useCropper({ imageEl, imageType })
+  const [cropData, setCropData] = useState<ImageCropData | null>(null)
+  const { currentZoom, zoomRange, zoomStep, handleZoomChange, cropImage } = useCropper({
+    imageEl,
+    imageType,
+    cropData,
+  })
+
+  const cropEditDisabled = !!cropData
 
   // not great - ideally we'd have a data flow trigger this via prop change
   // however, since there's no way to detect whether the file pick succeeds, the component wouldn't be able to report back whether it was actually opened
   // because of that we're letting the consumer trigger the open manually
   useImperativeHandle(ref, () => ({
-    open: (file) => {
+    open: (file, cropData) => {
       if (file) {
         const fileUrl = URL.createObjectURL(file)
         setEditedImageHref(fileUrl)
         setShowDialog(true)
       } else {
         inputRef.current?.click()
+        if (cropData) setCropData(cropData)
       }
     },
   }))
@@ -97,7 +108,11 @@ const ImageCropDialogComponent: React.ForwardRefRenderFunction<
 
   const zoomControlNode = (
     <ZoomControl>
-      <IconButton variant="tertiary" onClick={() => handleZoomChange(currentZoom - zoomStep)}>
+      <IconButton
+        variant="tertiary"
+        onClick={() => handleZoomChange(currentZoom - zoomStep)}
+        disabled={cropEditDisabled}
+      >
         <SvgGlyphZoomOut />
       </IconButton>
       <StyledSlider
@@ -106,8 +121,13 @@ const ImageCropDialogComponent: React.ForwardRefRenderFunction<
         min={zoomRange[0]}
         max={zoomRange[1]}
         step={zoomStep}
+        disabled={cropEditDisabled}
       />
-      <IconButton variant="tertiary" onClick={() => handleZoomChange(currentZoom + zoomStep)}>
+      <IconButton
+        variant="tertiary"
+        onClick={() => handleZoomChange(currentZoom + zoomStep)}
+        disabled={cropEditDisabled}
+      >
         <SvgGlyphZoomIn />
       </IconButton>
     </ZoomControl>
@@ -131,7 +151,7 @@ const ImageCropDialogComponent: React.ForwardRefRenderFunction<
           <AlignInfo variant="body2">Drag and adjust image position</AlignInfo>
         </AlignInfoContainer>
         {editedImageHref && (
-          <CropContainer rounded={imageType === 'avatar'}>
+          <CropContainer rounded={imageType === 'avatar'} disabled={cropEditDisabled}>
             <StyledImage src={editedImageHref} ref={imageElRefCallback} />
           </CropContainer>
         )}

+ 18 - 6
src/components/Dialogs/ImageCropDialog/cropper.ts

@@ -1,7 +1,8 @@
-import { useEffect, useState } from 'react'
 import Cropper from 'cropperjs'
-import { AssetDimensions, ImageCropData } from '@/types/cropper'
 import 'cropperjs/dist/cropper.min.css'
+import { useEffect, useState } from 'react'
+
+import { AssetDimensions, ImageCropData } from '@/types/cropper'
 
 const MAX_ZOOM = 3
 
@@ -10,6 +11,7 @@ export type CropperImageType = 'avatar' | 'videoThumbnail' | 'cover'
 type UseCropperOpts = {
   imageEl: HTMLImageElement | null
   imageType: CropperImageType
+  cropData?: ImageCropData | null
 }
 
 const ASPECT_RATIO_PER_TYPE: Record<CropperImageType, number> = {
@@ -45,7 +47,7 @@ const CANVAS_OPTS_PER_TYPE: Record<CropperImageType, Cropper.GetCroppedCanvasOpt
   },
 }
 
-export const useCropper = ({ imageEl, imageType }: UseCropperOpts) => {
+export const useCropper = ({ imageEl, imageType, cropData }: UseCropperOpts) => {
   const [cropper, setCropper] = useState<Cropper | null>(null)
   const [currentZoom, setCurrentZoom] = useState(0)
   const [zoomRange, setZoomRange] = useState<[number, number]>([0, 1])
@@ -90,6 +92,14 @@ export const useCropper = ({ imageEl, imageType }: UseCropperOpts) => {
 
       const middleZoom = minZoom + (maxZoom - minZoom) / 2
       cropper.zoomTo(middleZoom)
+
+      if (cropData) {
+        const { data, canvasData, cropBoxData, zoom } = cropData
+        cropper.zoomTo(zoom)
+        cropper.setCropBoxData(cropBoxData)
+        cropper.setCanvasData(canvasData)
+        cropper.setData(data)
+      }
     }
 
     const cropper = new Cropper(imageEl, {
@@ -110,7 +120,7 @@ export const useCropper = ({ imageEl, imageType }: UseCropperOpts) => {
     return () => {
       cropper.destroy()
     }
-  }, [imageEl, imageType])
+  }, [cropData, imageEl, imageType])
 
   // handle zoom event
   useEffect(() => {
@@ -144,8 +154,10 @@ export const useCropper = ({ imageEl, imageType }: UseCropperOpts) => {
         reject(new Error('No cropper instance'))
         return
       }
-
-      const imageCropData = cropper.getCropBoxData()
+      const data = cropper.getData()
+      const cropBoxData = cropper.getCropBoxData()
+      const canvasData = cropper.getCanvasData()
+      const imageCropData = { data, cropBoxData, canvasData, zoom: currentZoom }
       const canvas = cropper.getCroppedCanvas(CANVAS_OPTS_PER_TYPE[imageType])
       const assetDimensions = {
         width: canvas.width,

+ 4 - 2
src/components/Dialogs/MessageDialog/MessageDialog.stories.tsx

@@ -1,8 +1,10 @@
-import React from 'react'
-import MessageDialog, { MessageDialogProps } from './MessageDialog'
 import { Story, Meta } from '@storybook/react'
+import React from 'react'
+
 import { OverlayManagerProvider } from '@/hooks/useOverlayManager'
 
+import MessageDialog, { MessageDialogProps } from './MessageDialog'
+
 export default {
   title: 'General/MessageDialog',
   component: MessageDialog,

+ 2 - 1
src/components/Dialogs/MessageDialog/MessageDialog.style.ts

@@ -1,6 +1,7 @@
 import styled from '@emotion/styled'
-import { colors, sizes } from '@/shared/theme'
+
 import { Text } from '@/shared/components'
+import { colors, sizes } from '@/shared/theme'
 
 export const MessageIconWrapper = styled.div`
   margin-bottom: ${sizes(4)};

+ 6 - 3
src/components/Dialogs/MessageDialog/MessageDialog.tsx

@@ -1,14 +1,17 @@
 import React, { ReactNode } from 'react'
-import ActionDialog, { ActionDialogProps } from '../ActionDialog/ActionDialog'
-import { StyledTitleText, StyledDescriptionText, MessageIconWrapper } from './MessageDialog.style'
+
 import { SvgOutlineError, SvgOutlineSuccess, SvgOutlineWarning } from '@/shared/icons'
 
+import { StyledTitleText, StyledDescriptionText, MessageIconWrapper } from './MessageDialog.style'
+
+import ActionDialog, { ActionDialogProps } from '../ActionDialog/ActionDialog'
+
 type DialogVariant = 'success' | 'warning' | 'error' | 'info'
 
 export type MessageDialogProps = {
   variant?: DialogVariant
   title?: string
-  description?: string
+  description?: React.ReactNode
   icon?: React.ReactElement
 } & ActionDialogProps
 

+ 4 - 2
src/components/Dialogs/Multistepper/Multistepper.stories.tsx

@@ -1,9 +1,11 @@
-import React, { useState } from 'react'
-import Multistepper from './Multistepper'
 import { Story, Meta } from '@storybook/react'
+import React, { useState } from 'react'
+
 import { OverlayManagerProvider } from '@/hooks/useOverlayManager'
 import { Button } from '@/shared/components'
 
+import Multistepper from './Multistepper'
+
 export default {
   title: 'General/Multistepper',
   component: Multistepper,

+ 4 - 2
src/components/Dialogs/Multistepper/Multistepper.style.ts

@@ -1,8 +1,10 @@
 import styled from '@emotion/styled'
-import BaseDialog from '../BaseDialog'
+
 import { Text } from '@/shared/components'
-import { colors, sizes, media, typography } from '@/shared/theme'
 import { SvgGlyphChevronRight } from '@/shared/icons'
+import { colors, sizes, media, typography } from '@/shared/theme'
+
+import BaseDialog from '../BaseDialog'
 
 type CircleProps = {
   isFilled?: boolean

+ 6 - 3
src/components/Dialogs/Multistepper/Multistepper.tsx

@@ -1,5 +1,8 @@
 import React, { Fragment } from 'react'
-import { BaseDialogProps } from '../BaseDialog'
+
+import { Text } from '@/shared/components'
+import { SvgGlyphCheck } from '@/shared/icons'
+
 import {
   StyledDialog,
   StyledHeader,
@@ -10,8 +13,8 @@ import {
   StyledChevron,
   StyledStepTitle,
 } from './Multistepper.style'
-import { SvgGlyphCheck } from '@/shared/icons'
-import { Text } from '@/shared/components'
+
+import { BaseDialogProps } from '../BaseDialog'
 
 type Step = {
   title: string

+ 4 - 2
src/components/Dialogs/TransactionDialog/TransactionDialog.stories.tsx

@@ -1,9 +1,11 @@
-import React from 'react'
-import TransactionDialog, { TransactionDialogProps } from './TransactionDialog'
 import { Meta, Story } from '@storybook/react'
+import React from 'react'
+
 import { OverlayManagerProvider } from '@/hooks/useOverlayManager'
 import { ExtrinsicStatus } from '@/joystream-lib'
 
+import TransactionDialog, { TransactionDialogProps } from './TransactionDialog'
+
 export default {
   title: 'General/TransactionDialog',
   component: TransactionDialog,

+ 5 - 4
src/components/Dialogs/TransactionDialog/TransactionDialog.style.ts

@@ -1,8 +1,9 @@
+import { css } from '@emotion/react'
 import styled from '@emotion/styled'
-import { sizes, media, colors, transitions } from '@/shared/theme'
-import { ReactComponent as TransactionIllustration } from '@/assets/transaction-illustration.svg'
+
 import Spinner from '@/shared/components/Spinner'
-import { css } from '@emotion/react'
+import { SvgTransactionIllustration } from '@/shared/illustrations'
+import { sizes, media, colors, transitions } from '@/shared/theme'
 
 type StepProps = {
   isActive?: boolean
@@ -37,7 +38,7 @@ export const TextContainer = styled.div`
   position: relative;
 `
 
-export const StyledTransactionIllustration = styled(TransactionIllustration)`
+export const StyledTransactionIllustration = styled(SvgTransactionIllustration)`
   position: absolute;
   top: ${sizes(2)};
   left: -50px;

+ 12 - 41
src/components/Dialogs/TransactionDialog/TransactionDialog.tsx

@@ -1,15 +1,15 @@
 import React from 'react'
-import ActionDialog, { ActionDialogProps } from '../ActionDialog/ActionDialog'
-import { TextContainer, StyledTransactionIllustration, StyledSpinner, StepsBar, Step } from './TransactionDialog.style'
-import { StyledTitleText, StyledDescriptionText } from '../MessageDialog/MessageDialog.style'
+
 import { ExtrinsicStatus } from '@/joystream-lib'
-import MessageDialog from '../MessageDialog'
 import { Tooltip } from '@/shared/components'
 
+import { TextContainer, StyledTransactionIllustration, StyledSpinner, StepsBar, Step } from './TransactionDialog.style'
+
+import ActionDialog, { ActionDialogProps } from '../ActionDialog/ActionDialog'
+import { StyledTitleText, StyledDescriptionText } from '../MessageDialog/MessageDialog.style'
+
 export type TransactionDialogProps = Pick<ActionDialogProps, 'className'> & {
   status: ExtrinsicStatus | null
-  successTitle: string
-  successDescription: string
   onClose: () => void
 }
 
@@ -39,40 +39,11 @@ const TRANSACTION_STEPS_DETAILS = {
   },
 }
 
-const TransactionDialog: React.FC<TransactionDialogProps> = ({
-  status,
-  successTitle,
-  successDescription,
-  onClose,
-  ...actionDialogProps
-}) => {
-  if (status === ExtrinsicStatus.Error) {
-    return (
-      <MessageDialog
-        showDialog
-        variant="error"
-        title="Something went wrong..."
-        description="Some unexpected error was encountered. If this persists, our Discord community may be a good place to find some help."
-        secondaryButtonText="Close"
-        onSecondaryButtonClick={onClose}
-      />
-    )
-  }
-
-  if (status === ExtrinsicStatus.Completed) {
-    return (
-      <MessageDialog
-        showDialog
-        variant="success"
-        title={successTitle}
-        description={successDescription}
-        secondaryButtonText="Close"
-        onSecondaryButtonClick={onClose}
-      />
-    )
-  }
-
-  const stepDetails = status != null ? TRANSACTION_STEPS_DETAILS[status] : null
+const TransactionDialog: React.FC<TransactionDialogProps> = ({ status, onClose, ...actionDialogProps }) => {
+  const stepDetails =
+    status != null && status !== ExtrinsicStatus.Error && status !== ExtrinsicStatus.Completed
+      ? TRANSACTION_STEPS_DETAILS[status]
+      : null
 
   const canCancel = status === ExtrinsicStatus.ProcessingAssets || ExtrinsicStatus.Unsigned
 
@@ -82,7 +53,7 @@ const TransactionDialog: React.FC<TransactionDialogProps> = ({
 
   return (
     <ActionDialog
-      showDialog={status != null}
+      showDialog={!!stepDetails}
       onSecondaryButtonClick={onClose}
       secondaryButtonText="Cancel"
       secondaryButtonDisabled={!canCancel}

+ 2 - 2
src/components/Dialogs/index.ts

@@ -1,8 +1,8 @@
 import ActionDialog from './ActionDialog'
-import MessageDialog from './MessageDialog'
 import BaseDialog, { BaseDialogProps } from './BaseDialog'
-import Multistepper from './Multistepper'
 import ImageCropDialog, { ImageCropDialogImperativeHandle } from './ImageCropDialog'
+import MessageDialog from './MessageDialog'
+import Multistepper from './Multistepper'
 import TransactionDialog from './TransactionDialog'
 
 export { BaseDialog, ActionDialog, Multistepper, ImageCropDialog, TransactionDialog, MessageDialog }

+ 1 - 2
src/components/ErrorFallback.tsx

@@ -1,7 +1,6 @@
-import React from 'react'
-
 import styled from '@emotion/styled'
 import { FallbackRender } from '@sentry/react/dist/errorboundary'
+import React from 'react'
 
 import { Button, Text } from '@/shared/components'
 import { sizes, colors } from '@/shared/theme'

+ 5 - 4
src/components/InfiniteGrids/InfiniteChannelGrid.tsx

@@ -1,9 +1,7 @@
-import React, { useCallback, useState } from 'react'
-import styled from '@emotion/styled'
 import { css } from '@emotion/react'
+import styled from '@emotion/styled'
+import React, { useCallback, useState } from 'react'
 
-import { sizes } from '@/shared/theme'
-import { Grid, Text } from '@/shared/components'
 import {
   GetChannelsConnectionDocument,
   GetChannelsConnectionQuery,
@@ -11,6 +9,9 @@ import {
   AssetAvailability,
 } from '@/api/queries'
 import ChannelPreview from '@/components/ChannelPreview'
+import { Grid, Text } from '@/shared/components'
+import { sizes } from '@/shared/theme'
+
 import useInfiniteGrid from './useInfiniteGrid'
 
 type InfiniteChannelGridProps = {

+ 5 - 4
src/components/InfiniteGrids/InfiniteVideoGrid.tsx

@@ -1,9 +1,6 @@
-import React, { useCallback, useEffect, useState } from 'react'
 import styled from '@emotion/styled'
+import React, { useCallback, useEffect, useState } from 'react'
 
-import { sizes } from '@/shared/theme'
-import { Grid, Text, Placeholder } from '@/shared/components'
-import VideoPreview from '@/components/VideoPreview'
 import {
   GetVideosConnectionDocument,
   GetVideosConnectionQuery,
@@ -11,6 +8,10 @@ import {
   VideoWhereInput,
   AssetAvailability,
 } from '@/api/queries'
+import VideoPreview from '@/components/VideoPreview'
+import { Grid, Text, Placeholder } from '@/shared/components'
+import { sizes } from '@/shared/theme'
+
 import useInfiniteGrid from './useInfiniteGrid'
 
 type InfiniteVideoGridProps = {

+ 1 - 1
src/components/InfiniteGrids/index.ts

@@ -1,4 +1,4 @@
-import InfiniteVideoGrid from './InfiniteVideoGrid'
 import InfiniteChannelGrid from './InfiniteChannelGrid'
+import InfiniteVideoGrid from './InfiniteVideoGrid'
 
 export { InfiniteVideoGrid, InfiniteChannelGrid }

+ 3 - 3
src/components/InfiniteGrids/useInfiniteGrid.ts

@@ -1,8 +1,8 @@
-import { useEffect } from 'react'
 import { ApolloError, useQuery } from '@apollo/client'
-import { debounce } from 'lodash'
-import { DocumentNode } from 'graphql'
 import { TypedDocumentNode } from '@graphql-typed-document-node/core'
+import { DocumentNode } from 'graphql'
+import { debounce } from 'lodash'
+import { useEffect } from 'react'
 
 type PaginatedData<T> = {
   edges: {

+ 2 - 2
src/components/InterruptedVideosGallery.tsx

@@ -1,8 +1,8 @@
-import React from 'react'
 import { RouteComponentProps } from '@reach/router'
+import React from 'react'
 
-import { usePersonalData } from '@/hooks'
 import { VideoGallery } from '@/components'
+import { usePersonalData } from '@/hooks'
 
 const INTERRUPTED_VIDEOS_COUNT = 16
 

+ 2 - 1
src/components/Link/Link.style.ts

@@ -1,5 +1,6 @@
-import { Link } from 'react-router-dom'
 import styled from '@emotion/styled'
+import { Link } from 'react-router-dom'
+
 import { typography, colors } from '@/shared/theme'
 
 export const StyledLink = styled(Link)`

+ 1 - 0
src/components/Link/Link.tsx

@@ -1,4 +1,5 @@
 import React, { ReactChild } from 'react'
+
 import { DisabledLabel, StyledLink } from './Link.style'
 
 export type LinkProps = {

+ 1 - 0
src/components/Link/index.ts

@@ -1,2 +1,3 @@
 import Link from './Link'
+
 export default Link

+ 9 - 5
src/components/NoConnectionIndicator/NoConnectionIndicator.stories.tsx

@@ -1,7 +1,9 @@
-import React from 'react'
 import { Story, Meta } from '@storybook/react'
+import React from 'react'
+
+import { ConnectionStatusProvider, SnackbarProvider } from '@/hooks'
+
 import NoConnectionIndicator, { NoConnectionIndicatorProps } from './NoConnectionIndicator'
-import { ConnectionStatusProvider } from '@/hooks'
 
 export default {
   title: 'General/NoConnectionIndicator',
@@ -11,9 +13,11 @@ export default {
   },
   decorators: [
     (Story) => (
-      <ConnectionStatusProvider>
-        <Story />
-      </ConnectionStatusProvider>
+      <SnackbarProvider>
+        <ConnectionStatusProvider>
+          <Story />
+        </ConnectionStatusProvider>
+      </SnackbarProvider>
     ),
   ],
 } as Meta

+ 3 - 2
src/components/NoConnectionIndicator/NoConnectionIndicator.style.ts

@@ -1,7 +1,8 @@
-import { colors, sizes, zIndex, media } from '@/shared/theme'
-import { TOP_NAVBAR_HEIGHT } from '@/components'
 import styled from '@emotion/styled'
 
+import { TOP_NAVBAR_HEIGHT } from '@/components'
+import { colors, sizes, zIndex, media } from '@/shared/theme'
+
 export const TextWrapper = styled.div`
   width: 100%;
   display: flex;

+ 4 - 2
src/components/NoConnectionIndicator/NoConnectionIndicator.tsx

@@ -1,9 +1,11 @@
+import React from 'react'
+import { CSSTransition } from 'react-transition-group'
+
 import { ConnectionStatus } from '@/hooks'
 import { Text } from '@/shared/components'
 import { SvgAlertWarning } from '@/shared/icons'
 import { transitions } from '@/shared/theme'
-import React from 'react'
-import { CSSTransition } from 'react-transition-group'
+
 import { IndicatorWrapper, TextWrapper, IconWrapper } from './NoConnectionIndicator.style'
 
 export type NoConnectionIndicatorProps = {

+ 1 - 0
src/components/NoConnectionIndicator/index.ts

@@ -1,2 +1,3 @@
 import NoConnectionIndicator from './NoConnectionIndicator'
+
 export default NoConnectionIndicator

+ 1 - 0
src/components/PlaceholderVideoGrid.tsx

@@ -1,6 +1,7 @@
 import React from 'react'
 
 import { Grid } from '@/shared/components'
+
 import VideoPreview from './VideoPreview'
 
 type PlaceholderVideoGridProps = {

+ 5 - 4
src/components/Sidenav/SidenavBase.style.ts

@@ -1,10 +1,11 @@
-import { ReactComponent as UnstyledFullLogo } from '@/assets/full-logo.svg'
-import { Text } from '@/shared/components'
-import { colors, sizes, transitions, typography, zIndex, media } from '@/shared/theme'
 import isPropValid from '@emotion/is-prop-valid'
 import styled from '@emotion/styled'
 import { Link, LinkProps } from 'react-router-dom'
+
+import { Text } from '@/shared/components'
 import { badgeStyles } from '@/shared/components/Badge'
+import { SvgJoystreamFullLogo } from '@/shared/illustrations'
+import { colors, sizes, transitions, typography, zIndex, media } from '@/shared/theme'
 
 export const EXPANDED_SIDENAVBAR_WIDTH = 360
 export const NAVBAR_LEFT_PADDING = 24
@@ -54,7 +55,7 @@ export const LogoLink = styled(Link)`
   }
 `
 
-export const Logo = styled(UnstyledFullLogo)`
+export const Logo = styled(SvgJoystreamFullLogo)`
   height: ${sizes(8)};
 `
 

+ 6 - 4
src/components/Sidenav/SidenavBase.tsx

@@ -1,6 +1,12 @@
 import React, { ReactNode } from 'react'
 import { useMatch } from 'react-router-dom'
+import { CSSTransition } from 'react-transition-group'
 import useResizeObserver from 'use-resize-observer'
+
+import { absoluteRoutes } from '@/config/routes'
+import HamburgerButton from '@/shared/components/HamburgerButton'
+import { transitions } from '@/shared/theme'
+
 import {
   SidebarNav,
   SidebarNavList,
@@ -17,10 +23,6 @@ import {
   StudioText,
   LegalLinksWrapper,
 } from './SidenavBase.style'
-import { CSSTransition } from 'react-transition-group'
-import { transitions } from '@/shared/theme'
-import HamburgerButton from '@/shared/components/HamburgerButton'
-import { absoluteRoutes } from '@/config/routes'
 
 type NavSubitem = {
   name: string

+ 32 - 34
src/components/Sidenav/StudioSidenav/StudioSidenav.tsx

@@ -1,12 +1,13 @@
 import React, { useState } from 'react'
-import { useDrafts, useAuthorizedUser, useEditVideoSheet, useUploadsManager, useDisplayDataLostWarning } from '@/hooks'
+import { useNavigate } from 'react-router'
+import { CSSTransition } from 'react-transition-group'
+
+import SidenavBase, { NavItemType } from '@/components/Sidenav/SidenavBase'
 import { absoluteRoutes } from '@/config/routes'
+import { useDrafts, useAuthorizedUser, useEditVideoSheet, useUploadsManager, useDisplayDataLostWarning } from '@/hooks'
 import { Button } from '@/shared/components'
-import SidenavBase, { NavItemType } from '@/components/Sidenav/SidenavBase'
 import { SvgGlyphAddVideo, SvgGlyphExternal, SvgNavChannel, SvgNavUpload, SvgNavVideos } from '@/shared/icons'
-import { CSSTransition } from 'react-transition-group'
 import { transitions } from '@/shared/theme'
-import { useNavigate } from 'react-router'
 
 const studioNavbarItems: NavItemType[] = [
   {
@@ -33,11 +34,11 @@ export const StudioSidenav: React.FC = () => {
   const [expanded, setExpanded] = useState(false)
   const { activeChannelId } = useAuthorizedUser()
   const { unseenDrafts } = useDrafts('video', activeChannelId)
-  const { uploadsState } = useUploadsManager(activeChannelId)
+  const { uploadsState } = useUploadsManager()
   const navigate = useNavigate()
   const { sheetState } = useEditVideoSheet()
 
-  const { DataLostWarningDialog, openWarningDialog } = useDisplayDataLostWarning()
+  const { openWarningDialog } = useDisplayDataLostWarning()
 
   const assetsInProgress = uploadsState.flat().filter((asset) => asset.lastStatus === 'inProgress')
 
@@ -67,35 +68,32 @@ export const StudioSidenav: React.FC = () => {
   }
 
   return (
-    <>
-      <SidenavBase
-        expanded={expanded}
-        toggleSideNav={setExpanded}
-        isStudio
-        items={studioNavbarItemsWithBadge}
-        buttonsContent={
-          <>
-            <CSSTransition
-              in={sheetState !== 'open'}
-              unmountOnExit
-              timeout={parseInt(transitions.timings.loading)}
-              classNames={transitions.names.fade}
+    <SidenavBase
+      expanded={expanded}
+      toggleSideNav={setExpanded}
+      isStudio
+      items={studioNavbarItemsWithBadge}
+      buttonsContent={
+        <>
+          <CSSTransition
+            in={sheetState !== 'open'}
+            unmountOnExit
+            timeout={parseInt(transitions.timings.loading)}
+            classNames={transitions.names.fade}
+          >
+            <Button
+              icon={<SvgGlyphAddVideo />}
+              to={absoluteRoutes.studio.editVideo()}
+              onClick={() => setExpanded(false)}
             >
-              <Button
-                icon={<SvgGlyphAddVideo />}
-                to={absoluteRoutes.studio.editVideo()}
-                onClick={() => setExpanded(false)}
-              >
-                New Video
-              </Button>
-            </CSSTransition>
-            <Button variant="secondary" onClick={handleClick} icon={<SvgGlyphExternal />}>
-              Joystream
+              New Video
             </Button>
-          </>
-        }
-      />
-      <DataLostWarningDialog />
-    </>
+          </CSSTransition>
+          <Button variant="secondary" onClick={handleClick} icon={<SvgGlyphExternal />}>
+            Joystream
+          </Button>
+        </>
+      }
+    />
   )
 }

+ 4 - 2
src/components/Sidenav/ViewerSidenav/FollowedChannels.style.ts

@@ -1,7 +1,9 @@
-import { sizes, colors, typography } from '@/shared/theme'
 import styled from '@emotion/styled'
-import ChannelLink from '../../ChannelLink'
+
 import { Text } from '@/shared/components'
+import { sizes, colors, typography } from '@/shared/theme'
+
+import ChannelLink from '../../ChannelLink'
 import { NAVBAR_LEFT_PADDING, EXPANDED_SIDENAVBAR_WIDTH } from '../SidenavBase.style'
 
 export const FollowedChannelsWrapper = styled.div`

+ 6 - 4
src/components/Sidenav/ViewerSidenav/FollowedChannels.tsx

@@ -1,4 +1,10 @@
 import React, { useState } from 'react'
+import { CSSTransition } from 'react-transition-group'
+
+import { FollowedChannel } from '@/hooks/usePersonalData/localStorageClient/types'
+import { SvgGlyphChevronDown, SvgGlyphChevronUp } from '@/shared/icons'
+import { transitions } from '@/shared/theme'
+
 import {
   ChannelsWrapper,
   ChannelsTitle,
@@ -9,10 +15,6 @@ import {
   ShowMoreIconWrapper,
   FollowedChannelsWrapper,
 } from './FollowedChannels.style'
-import { transitions } from '@/shared/theme'
-import { CSSTransition } from 'react-transition-group'
-import { FollowedChannel } from '@/hooks/usePersonalData/localStorageClient/types'
-import { SvgGlyphChevronDown, SvgGlyphChevronUp } from '@/shared/icons'
 
 const MAX_CHANNELS = 4
 

+ 5 - 3
src/components/Sidenav/ViewerSidenav/ViewerSidenav.tsx

@@ -1,11 +1,13 @@
 import React, { useState } from 'react'
-import { usePersonalData } from '@/hooks'
+
+import SidenavBase, { NavItemType } from '@/components/Sidenav/SidenavBase'
 import { absoluteRoutes } from '@/config/routes'
+import { usePersonalData } from '@/hooks'
 import { Button } from '@/shared/components'
-import SidenavBase, { NavItemType } from '@/components/Sidenav/SidenavBase'
-import FollowedChannels from './FollowedChannels'
 import { SvgGlyphExternal, SvgNavChannels, SvgNavHome, SvgNavVideos } from '@/shared/icons'
 
+import FollowedChannels from './FollowedChannels'
+
 const viewerSidenavItems: NavItemType[] = [
   {
     icon: <SvgNavHome />,

+ 5 - 4
src/components/SignInSteps/AccountStep.style.ts

@@ -1,7 +1,10 @@
+import styled from '@emotion/styled'
+
 import { Button, Text, RadioButton } from '@/shared/components'
 import Spinner from '@/shared/components/Spinner'
+import { SvgAccountCreationIllustration } from '@/shared/illustrations'
 import { sizes, colors, typography, transitions, media } from '@/shared/theme'
-import styled from '@emotion/styled'
+
 import { StepWrapper } from './SignInSteps.style'
 
 type AccountWrapperProps = {
@@ -24,9 +27,7 @@ export const IconGroup = styled.div`
   }
 `
 
-export const AccountStepImg = styled.img`
-  object-fit: cover;
-  max-width: 100%;
+export const AccountStepImg = styled(SvgAccountCreationIllustration)`
   height: 210px;
 `
 

+ 19 - 12
src/components/SignInSteps/AccountStep.tsx

@@ -1,9 +1,12 @@
-import accountCreation from '@/assets/account-creation.svg'
+import React, { FormEvent, useState } from 'react'
+import { useNavigate } from 'react-router'
+import { CSSTransition, SwitchTransition } from 'react-transition-group'
+
 import { useUser } from '@/hooks'
 import { Text } from '@/shared/components'
+import { SvgGlyphChannel, SvgOutlineConnect } from '@/shared/icons'
 import { transitions } from '@/shared/theme'
-import React, { FormEvent, useState } from 'react'
-import { CSSTransition, SwitchTransition } from 'react-transition-group'
+
 import {
   StyledSpinner,
   AccountStepImg,
@@ -20,11 +23,15 @@ import {
   SubTitle,
   StyledStepWrapper,
 } from './AccountStep.style'
-import polkadotIcon from '@/assets/polkadot-logo.svg'
-import joystreamIcon from '@/assets/joystream-logo.svg'
-import { StepFooter, BottomBarIcon, StepSubTitle, StepTitle, StepWrapper, StyledLogo } from './SignInSteps.style'
-import { useNavigate } from 'react-router'
-import { SvgGlyphChannel, SvgOutlineConnect } from '@/shared/icons'
+import {
+  StepFooter,
+  BottomBarIcon,
+  StepSubTitle,
+  StepTitle,
+  StepWrapper,
+  StyledPolkadotLogo,
+  StyledJoystreamLogo,
+} from './SignInSteps.style'
 
 type AccountStepProps = {
   nextStepPath: string
@@ -61,8 +68,8 @@ const AccountStep: React.FC<AccountStepProps> = ({ nextStepPath }) => {
         timeout={parseInt(transitions.timings.routing)}
       >
         {!accountsWithNoMembership?.length ? (
-          <StyledStepWrapper withBottomBar>
-            <AccountStepImg src={accountCreation} />
+          <StyledStepWrapper>
+            <AccountStepImg />
             <StepTitle variant="h4">Create blockchain account</StepTitle>
             <SubTitle variant="body2" secondary>
               Use the Polkadot extension to generate your personal keypair. Follow these instructions:
@@ -89,9 +96,9 @@ const AccountStep: React.FC<AccountStepProps> = ({ nextStepPath }) => {
           <form onSubmit={handleSubmitSelectedAccount}>
             <StepWrapper>
               <IconGroup>
-                <StyledLogo src={polkadotIcon} alt="Polkadot icon" />
+                <StyledPolkadotLogo />
                 <SvgOutlineConnect />
-                <StyledLogo src={joystreamIcon} alt="Joystream icon" />
+                <StyledJoystreamLogo />
               </IconGroup>
               <StepTitle variant="h4">Connect account</StepTitle>
               <StepSubTitle secondary>

+ 24 - 4
src/components/SignInSteps/ExtensionStep.style.ts

@@ -1,8 +1,28 @@
-import { Button } from '@/shared/components'
-import { sizes } from '@/shared/theme'
 import styled from '@emotion/styled'
 
+import { Button, Text } from '@/shared/components'
+import { sizes } from '@/shared/theme'
+
+import { StepFooter } from './SignInSteps.style'
+
 export const StyledButton = styled(Button)`
-  margin-top: ${sizes(4)};
-  margin-bottom: ${sizes(12)};
+  margin: ${sizes(4)} 0;
+`
+
+export const StyledStepFooter = styled(StepFooter)`
+  margin-top: ${sizes(12)};
+`
+
+export const StyledListItem = styled(Text)`
+  text-align: left;
+  & + & {
+    margin-top: ${sizes(2)};
+  }
+`
+
+export const PolkadotExtensionRejectedWrapper = styled.div`
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  text-align: center;
 `

+ 42 - 9
src/components/SignInSteps/ExtensionStep.tsx

@@ -1,12 +1,13 @@
 import React, { useEffect } from 'react'
-import { StyledButton } from './ExtensionStep.style'
-import { StepFooter, BottomBarIcon, StepSubTitle, StepTitle, StepWrapper, StyledLogo } from './SignInSteps.style'
-import polkadotIcon from '@/assets/polkadot-logo.svg'
-import { Text } from '@/shared/components'
 import { useNavigate } from 'react-router'
-import { useRouterQuery, useUser } from '@/hooks'
+
+import { useRouterQuery, useUser, useDialog } from '@/hooks'
+import { Text, Button } from '@/shared/components'
 import { SvgGlyphExternal } from '@/shared/icons'
 
+import { StyledButton, StyledStepFooter, StyledListItem, PolkadotExtensionRejectedWrapper } from './ExtensionStep.style'
+import { BottomBarIcon, StepSubTitle, StepTitle, StepWrapper, StyledPolkadotLogo } from './SignInSteps.style'
+
 type ExtensionStepProps = {
   nextStepPath: string
 }
@@ -15,6 +16,10 @@ const ExtensionStep: React.FC<ExtensionStepProps> = ({ nextStepPath }) => {
   const navigate = useNavigate()
   const step = useRouterQuery('step')
   const { extensionConnected } = useUser()
+  const [openEnableExtensionDialog, closeEnableExtensionDialog] = useDialog({
+    description: <PolkadotExtensionRejected />,
+    onExitClick: () => closeEnableExtensionDialog(),
+  })
 
   useEffect(() => {
     if (extensionConnected && step === '1') {
@@ -23,8 +28,8 @@ const ExtensionStep: React.FC<ExtensionStepProps> = ({ nextStepPath }) => {
   }, [extensionConnected, navigate, nextStepPath, step])
 
   return (
-    <StepWrapper withBottomBar>
-      <StyledLogo src={polkadotIcon} alt="polkadot icon" />
+    <StepWrapper>
+      <StyledPolkadotLogo />
       <StepTitle variant="h4">Add Polkadot extension</StepTitle>
       <StepSubTitle secondary variant="body2">
         To manage your blockchain account, you will need a Polkadot browser extension. Please install it using the
@@ -33,14 +38,42 @@ const ExtensionStep: React.FC<ExtensionStepProps> = ({ nextStepPath }) => {
       <StyledButton icon={<SvgGlyphExternal />} to="https://polkadot.js.org/extension/">
         Install extension
       </StyledButton>
-      <StepFooter>
+      <Button variant="tertiary" size="small" onClick={() => openEnableExtensionDialog()}>
+        Polkadot extension already installed? Click here
+      </Button>
+      <StyledStepFooter>
         <BottomBarIcon />
         <Text variant="body2" secondary>
           Please reload the page and allow access after installing the extension
         </Text>
-      </StepFooter>
+      </StyledStepFooter>
     </StepWrapper>
   )
 }
 
+export const PolkadotExtensionRejected: React.FC = () => (
+  <PolkadotExtensionRejectedWrapper>
+    <StyledPolkadotLogo />
+    <StepTitle variant="h4">Allow Polkadot extension access</StepTitle>
+    <StepSubTitle secondary variant="body2">
+      If you’ve denied Polkadot extension access for this website, you won’t be able to use Joystream studio. To allow
+      access, you can take the following steps:
+    </StepSubTitle>
+    <ol>
+      <StyledListItem secondary as="li" variant="caption">
+        Open the extension popup with the icon in your browser bar
+      </StyledListItem>
+      <StyledListItem secondary as="li" variant="caption">
+        Use cog icon in upper right corner to open settings and select {'"Manage Website Access"'}
+      </StyledListItem>
+      <StyledListItem secondary as="li" variant="caption">
+        Find play.joystream.org address and switch it to allowed
+      </StyledListItem>
+      <StyledListItem secondary as="li" variant="caption">
+        Reload the page
+      </StyledListItem>
+    </ol>
+  </PolkadotExtensionRejectedWrapper>
+)
+
 export default ExtensionStep

+ 10 - 7
src/components/SignInSteps/SignInSteps.style.ts

@@ -1,13 +1,11 @@
+import styled from '@emotion/styled'
+
 import { Text } from '@/shared/components'
 import { SvgGlyphWarning } from '@/shared/icons/GlyphWarning'
+import { SvgJoystreamLogo, SvgPolkadotLogo } from '@/shared/illustrations'
 import { sizes, colors } from '@/shared/theme'
-import styled from '@emotion/styled'
-
-type StepWrapperProps = {
-  withBottomBar?: boolean
-}
 
-export const StepWrapper = styled.div<StepWrapperProps>`
+export const StepWrapper = styled.div`
   width: 100%;
   text-align: center;
   display: flex;
@@ -17,7 +15,11 @@ export const StepWrapper = styled.div<StepWrapperProps>`
   margin-top: ${sizes(10)};
 `
 
-export const StyledLogo = styled.img`
+export const StyledJoystreamLogo = styled(SvgJoystreamLogo)`
+  height: 40px;
+  width: 40px;
+`
+export const StyledPolkadotLogo = styled(SvgPolkadotLogo)`
   height: 40px;
   width: 40px;
 `
@@ -43,5 +45,6 @@ export const StepFooter = styled.div`
   align-items: center;
 `
 export const BottomBarIcon = styled(SvgGlyphWarning)`
+  flex-shrink: 0;
   margin-right: ${sizes(2)};
 `

+ 5 - 2
src/components/SignInSteps/SignInStepsStepper.tsx

@@ -1,11 +1,14 @@
-import { useRouterQuery } from '@/hooks'
 import React from 'react'
 import { useNavigate } from 'react-router'
-import { Multistepper } from '../Dialogs'
+
+import { useRouterQuery } from '@/hooks'
+
 import AccountStep from './AccountStep'
 import ExtensionStep from './ExtensionStep'
 import TermsStep from './TermsStep'
 
+import { Multistepper } from '../Dialogs'
+
 const SignInStepsStepper: React.FC = () => {
   const navigate = useNavigate()
   const step = Number(useRouterQuery('step'))

+ 2 - 1
src/components/SignInSteps/TermsStep.style.tsx

@@ -1,7 +1,8 @@
+import styled from '@emotion/styled'
 import React from 'react'
+
 import { Button, IconButton } from '@/shared/components'
 import { colors, sizes } from '@/shared/theme'
-import styled from '@emotion/styled'
 
 export const TermsBox = styled.div`
   scroll-behavior: smooth;

+ 5 - 3
src/components/SignInSteps/TermsStep.tsx

@@ -1,11 +1,13 @@
+import React, { useEffect, useRef, useState } from 'react'
+import { CSSTransition } from 'react-transition-group'
+
+import TermsOfService from '@/components/TermsOfService'
 import { absoluteRoutes } from '@/config/routes'
 import { SvgGlyphChevronDown } from '@/shared/icons'
 import { transitions } from '@/shared/theme'
-import React, { useEffect, useRef, useState } from 'react'
-import { CSSTransition } from 'react-transition-group'
+
 import { StepFooter, StepWrapper } from './SignInSteps.style'
 import { TermsBox, TextWrapper, TermsOverlay, ScrollButton, ContinueButton } from './TermsStep.style'
-import TermsOfService from '@/components/TermsOfService'
 
 const TermsStep: React.FC = () => {
   const [hasScrolledToBottom, setHasScrolledToBottom] = useState(false)

+ 4 - 3
src/components/StudioEntrypoint.tsx

@@ -1,10 +1,11 @@
-import React from 'react'
 import styled from '@emotion/styled'
-import { Spinner, Text } from '@/shared/components'
+import React from 'react'
+import { Navigate } from 'react-router-dom'
+
 import { TOP_NAVBAR_HEIGHT } from '@/components'
 import { absoluteRoutes } from '@/config/routes'
 import { useUser } from '@/hooks'
-import { Navigate } from 'react-router-dom'
+import { Spinner, Text } from '@/shared/components'
 
 const DEFAULT_ROUTE = absoluteRoutes.studio.videos()
 

+ 1 - 0
src/components/TermsOfService.tsx

@@ -1,4 +1,5 @@
 import React from 'react'
+
 import { LegalLastUpdateText, LegalListItem, LegalParagraph, Text } from '@/shared/components'
 
 const TermsOfService: React.FC = () => {

+ 3 - 1
src/components/Topbar/StudioTopbar/StudioTopbar.style.tsx

@@ -1,7 +1,9 @@
 import styled from '@emotion/styled'
 import { Link } from 'react-router-dom'
-import { media, colors, sizes, transitions, typography, zIndex } from '@/shared/theme'
+
 import { Avatar, Text, Placeholder } from '@/shared/components'
+import { media, colors, sizes, transitions, typography, zIndex } from '@/shared/theme'
+
 import TopbarBase from '../TopbarBase'
 import { TOP_NAVBAR_HEIGHT } from '../TopbarBase.style'
 

+ 6 - 6
src/components/Topbar/StudioTopbar/StudioTopbar.tsx

@@ -1,9 +1,13 @@
 import React, { useState, useEffect, useRef } from 'react'
-import { useUser, useDisplayDataLostWarning, useEditVideoSheet, useAsset } from '@/hooks'
+import { useNavigate } from 'react-router'
+import { CSSTransition } from 'react-transition-group'
+
 import { BasicChannelFieldsFragment } from '@/api/queries'
 import { absoluteRoutes } from '@/config/routes'
+import { useUser, useDisplayDataLostWarning, useEditVideoSheet, useAsset } from '@/hooks'
 import { Placeholder, Text, Button, ExpandButton, IconButton } from '@/shared/components'
 import { SvgGlyphAddVideo, SvgGlyphCheck, SvgGlyphLogOut, SvgGlyphNewChannel } from '@/shared/icons'
+import { transitions } from '@/shared/theme'
 
 import {
   StyledTopbarBase,
@@ -26,9 +30,6 @@ import {
   AvatarPlaceholder,
   GlyphCheckContainer,
 } from './StudioTopbar.style'
-import { CSSTransition } from 'react-transition-group'
-import { transitions } from '@/shared/theme'
-import { useNavigate } from 'react-router'
 
 type StudioTopbarProps = {
   hideChannelInfo?: boolean
@@ -71,7 +72,7 @@ const StudioTopbar: React.FC<StudioTopbarProps> = ({ hideChannelInfo, fullWidth
   const navigate = useNavigate()
 
   const { sheetState, setSheetState, anyVideoTabsCachedAssets } = useEditVideoSheet()
-  const { DataLostWarningDialog, openWarningDialog } = useDisplayDataLostWarning()
+  const { openWarningDialog } = useDisplayDataLostWarning()
 
   const currentChannel = activeMembership?.channels.find((channel) => channel.id === activeChannelId)
 
@@ -139,7 +140,6 @@ const StudioTopbar: React.FC<StudioTopbarProps> = ({ hideChannelInfo, fullWidth
 
   return (
     <>
-      <DataLostWarningDialog />
       <StyledTopbarBase variant="studio" fullWidth={fullWidth} isHamburgerButtonPresent={!!channelSet}>
         {!hideChannelInfo && (
           <StudioTopbarContainer>

+ 1 - 0
src/components/Topbar/StudioTopbar/index.ts

@@ -1,2 +1,3 @@
 import StudioTopbar from './StudioTopbar'
+
 export default StudioTopbar

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików