Browse Source

Content type modal (#4080)

* add content type dialog

* image fix

* handle all the buttons

* cr fixes
Bartosz Dryl 1 year ago
parent
commit
e4007bc5b3

BIN
packages/atlas/src/assets/images/discover-view.webp


+ 7 - 2
packages/atlas/src/components/_navigation/SidenavStudio/SidenavStudio.tsx

@@ -19,6 +19,7 @@ import { absoluteRoutes } from '@/config/routes'
 import { chanelUnseenDraftsSelector, useDraftStore } from '@/providers/drafts'
 import { useUploadsStore } from '@/providers/uploads/uploads.store'
 import { useUser } from '@/providers/user/user.hooks'
+import { useVideoWorkspace } from '@/providers/videoWorkspace'
 
 const studioNavbarItems: NavItemType[] = [
   {
@@ -72,6 +73,7 @@ type SidenavStudioProps = {
 
 export const SidenavStudio: FC<SidenavStudioProps> = ({ className }) => {
   const [expanded, setExpanded] = useState(false)
+  const { uploadVideoButtonProps } = useVideoWorkspace()
   const { channelId } = useUser()
   const unseenDrafts = useDraftStore(chanelUnseenDraftsSelector(channelId || ''))
 
@@ -95,8 +97,11 @@ export const SidenavStudio: FC<SidenavStudioProps> = ({ className }) => {
     <>
       <Button
         icon={<SvgActionAddVideo />}
-        onClick={() => setExpanded(false)}
-        to={absoluteRoutes.studio.videoWorkspace()}
+        to={uploadVideoButtonProps.to}
+        onClick={() => {
+          setExpanded(false)
+          uploadVideoButtonProps.onClick()
+        }}
       >
         Upload video
       </Button>

+ 2 - 3
packages/atlas/src/components/_navigation/TopbarStudio/TopbarStudio.tsx

@@ -27,7 +27,7 @@ export const TopbarStudio: FC<StudioTopbarProps> = ({ hideChannelInfo, isMembers
   const mdMatch = useMediaMatch('md')
   const hasAtLeastOneChannel = !!activeMembership?.channels.length && activeMembership?.channels.length >= 1
 
-  const { isWorkspaceOpen, setEditedVideo, setIsWorkspaceOpen } = useVideoWorkspace()
+  const { isWorkspaceOpen, setIsWorkspaceOpen, uploadVideoButtonProps } = useVideoWorkspace()
 
   const currentChannel = activeMembership?.channels.find((channel) => channel.id === channelId)
 
@@ -79,11 +79,10 @@ export const TopbarStudio: FC<StudioTopbarProps> = ({ hideChannelInfo, isMembers
                 classNames={transitions.names.fade}
               >
                 <Button
-                  to={absoluteRoutes.studio.videoWorkspace()}
-                  onClick={() => setEditedVideo()}
                   variant="secondary"
                   icon={<SvgActionAddVideo />}
                   iconPlacement="left"
+                  {...uploadVideoButtonProps}
                 >
                   {mdMatch && 'Upload video'}
                 </Button>

+ 21 - 0
packages/atlas/src/components/_overlays/ContentTypeDialog/ContentTypeDialog.styles.ts

@@ -0,0 +1,21 @@
+import styled from '@emotion/styled'
+
+import { cVar, sizes } from '@/styles'
+
+export const StyledImg = styled.img`
+  object-fit: cover;
+  min-width: 100%;
+`
+
+export const HeaderWrapper = styled.header`
+  padding: ${sizes(6)};
+`
+
+export const CheckboxWrapper = styled.div`
+  box-shadow: ${cVar('effectDividersTop')}, ${cVar('effectDividersBottom')};
+  background-color: ${cVar('colorBackgroundElevated')};
+  padding: ${sizes(4)} ${sizes(6)};
+  margin-bottom: ${sizes(6)};
+  display: flex;
+  align-items: center;
+`

+ 75 - 0
packages/atlas/src/components/_overlays/ContentTypeDialog/ContentTypeDialog.tsx

@@ -0,0 +1,75 @@
+import { FC } from 'react'
+import { Controller, useForm } from 'react-hook-form'
+
+import discoverView from '@/assets/images/discover-view.webp'
+import { Text } from '@/components/Text'
+import { Checkbox } from '@/components/_inputs/Checkbox'
+import { atlasConfig } from '@/config'
+
+import { CheckboxWrapper, HeaderWrapper, StyledImg } from './ContentTypeDialog.styles'
+
+import { DialogModal } from '../DialogModal'
+
+type ContentTypeDialogProps = {
+  onClose: () => void
+  isOpen: boolean
+  onSubmit: () => void
+}
+
+export const ContentTypeDialog: FC<ContentTypeDialogProps> = ({ onClose, isOpen, onSubmit }) => {
+  const { handleSubmit, control, reset } = useForm({
+    defaultValues: {
+      isSelected: false,
+    },
+  })
+  return (
+    <DialogModal
+      show={isOpen}
+      noContentPadding
+      primaryButton={{
+        text: 'Continue',
+        onClick: () => {
+          handleSubmit(() => {
+            onSubmit()
+          })()
+        },
+      }}
+      secondaryButton={{
+        text: 'Cancel',
+        onClick: () => {
+          reset({ isSelected: false })
+          onClose()
+        },
+      }}
+    >
+      <StyledImg src={discoverView} alt="Discover subpage" width={480} height={264} />
+      <HeaderWrapper>
+        <Text variant="h500" as="p" color="colorTextStrong">
+          Upload only {atlasConfig.general.appContentFocus} related content
+        </Text>
+        <Text variant="t200" as="p" color="colorText" margin={{ top: 2 }}>
+          Uploading any other type of content will result in taking down your channel. Please make sure that your
+          content falls under one of {atlasConfig.general.appName} categories before uploading.
+        </Text>
+      </HeaderWrapper>
+      <CheckboxWrapper>
+        <Controller
+          name="isSelected"
+          rules={{
+            required: { value: true, message: 'You have to agree to continue' },
+          }}
+          control={control}
+          render={({ field: { value, onChange }, fieldState: { error } }) => (
+            <Checkbox
+              caption={error?.message}
+              value={value}
+              label={`I will upload only ${atlasConfig.general.appContentFocus} related content`}
+              onChange={onChange}
+              error={!!error?.message}
+            />
+          )}
+        />
+      </CheckboxWrapper>
+    </DialogModal>
+  )
+}

+ 1 - 0
packages/atlas/src/components/_overlays/ContentTypeDialog/index.ts

@@ -0,0 +1 @@
+export * from './ContentTypeDialog'

+ 4 - 1
packages/atlas/src/providers/user/user.provider.tsx

@@ -150,6 +150,8 @@ export const UserProvider: FC<PropsWithChildren> = ({ children }) => {
   }, [accountId, lastUsedWalletName, memberId, signIn, walletStatus])
 
   const activeMembership = (memberId && memberships?.find((membership) => membership.id === memberId)) || null
+  const activeChannel =
+    (activeMembership && activeMembership?.channels.find((channel) => channel.id === channelId)) || null
 
   const isChannelBelongsToTheUserOrExists = activeMembership?.channels.length
     ? activeMembership.channels.some((channel) => channel.id === channelId)
@@ -166,11 +168,12 @@ export const UserProvider: FC<PropsWithChildren> = ({ children }) => {
       memberships: memberships || [],
       membershipsLoading,
       activeMembership,
+      activeChannel,
       isAuthLoading,
       signIn,
       refetchUserMemberships,
     }),
-    [memberships, activeMembership, isAuthLoading, signIn, refetchUserMemberships, membershipsLoading]
+    [memberships, membershipsLoading, activeMembership, activeChannel, isAuthLoading, signIn, refetchUserMemberships]
   )
 
   if (error) {

+ 1 - 0
packages/atlas/src/providers/user/user.types.ts

@@ -17,6 +17,7 @@ export type UserContextValue = {
   memberships: Membership[]
   membershipsLoading: boolean
   activeMembership: Membership | null
+  activeChannel: Membership['channels'][number] | null
 
   isAuthLoading: boolean
 

+ 49 - 2
packages/atlas/src/providers/videoWorkspace/provider.tsx

@@ -1,9 +1,16 @@
 import { FC, PropsWithChildren, createContext, useCallback, useMemo, useState } from 'react'
+import { useNavigate } from 'react-router'
 
+import { ContentTypeDialog } from '@/components/_overlays/ContentTypeDialog'
+import { atlasConfig } from '@/config'
+import { absoluteRoutes } from '@/config/routes'
 import { createId } from '@/utils/createId'
 
 import { ContextValue, VideoWorkspace } from './types'
 
+import { usePersonalDataStore } from '../personalData'
+import { useUser } from '../user/user.hooks'
+
 export const VideoWorkspaceContext = createContext<ContextValue | undefined>(undefined)
 VideoWorkspaceContext.displayName = 'VideoWorkspaceContext'
 
@@ -14,12 +21,35 @@ const generateVideo = () => ({
   mintNft: false,
 })
 
+const CONTENT_TYPE_INFO = 'content-type'
+
 export const VideoWorkspaceProvider: FC<PropsWithChildren> = ({ children }) => {
   const [editedVideoInfo, setEditedVideoInfo] = useState<VideoWorkspace>(generateVideo())
   const [isWorkspaceOpen, setIsWorkspaceOpen] = useState(false)
   const setEditedVideo = useCallback((video?: VideoWorkspace) => {
     setEditedVideoInfo(!video ? generateVideo() : video)
   }, [])
+  const navigate = useNavigate()
+
+  const { activeChannel } = useUser()
+
+  const isContentTypeInfoDismissed = usePersonalDataStore((state) =>
+    atlasConfig.general.appContentFocus
+      ? state.dismissedMessages.some((message) => message.id === CONTENT_TYPE_INFO) ||
+        (activeChannel?.totalVideosCreated && activeChannel?.totalVideosCreated > 0)
+      : true
+  )
+  const updateDismissedMessages = usePersonalDataStore((state) => state.actions.updateDismissedMessages)
+
+  const [isContentTypeDialogOpen, setIsContentDialogOpen] = useState(false)
+
+  const handleOpenVideoWorkspace = useCallback(() => {
+    if (!isContentTypeInfoDismissed) {
+      setIsContentDialogOpen(true)
+      return
+    }
+    setEditedVideo()
+  }, [isContentTypeInfoDismissed, setEditedVideo])
 
   const value = useMemo(
     () => ({
@@ -27,9 +57,26 @@ export const VideoWorkspaceProvider: FC<PropsWithChildren> = ({ children }) => {
       setEditedVideo,
       isWorkspaceOpen,
       setIsWorkspaceOpen,
+      uploadVideoButtonProps: {
+        to: isContentTypeInfoDismissed ? absoluteRoutes.studio.videoWorkspace() : undefined,
+        onClick: handleOpenVideoWorkspace,
+      },
     }),
-    [editedVideoInfo, setEditedVideo, isWorkspaceOpen]
+    [editedVideoInfo, setEditedVideo, isWorkspaceOpen, isContentTypeInfoDismissed, handleOpenVideoWorkspace]
   )
 
-  return <VideoWorkspaceContext.Provider value={value}>{children}</VideoWorkspaceContext.Provider>
+  return (
+    <VideoWorkspaceContext.Provider value={value}>
+      <ContentTypeDialog
+        isOpen={isContentTypeDialogOpen}
+        onClose={() => setIsContentDialogOpen(false)}
+        onSubmit={() => {
+          updateDismissedMessages(CONTENT_TYPE_INFO)
+          setIsContentDialogOpen(false)
+          navigate(absoluteRoutes.studio.videoWorkspace())
+        }}
+      />
+      {children}
+    </VideoWorkspaceContext.Provider>
+  )
 }

+ 4 - 0
packages/atlas/src/providers/videoWorkspace/types.ts

@@ -32,6 +32,10 @@ export type ContextValue = {
   setEditedVideo: (video?: VideoWorkspace) => void
   isWorkspaceOpen: boolean
   setIsWorkspaceOpen: (open: boolean) => void
+  uploadVideoButtonProps: {
+    to?: string
+    onClick: () => void
+  }
 }
 
 export type VideoWorkspaceVideoFormFields = {

+ 3 - 7
packages/atlas/src/views/studio/MyUploadsView/MyUploadsView.tsx

@@ -5,11 +5,11 @@ import { SvgActionUpload } from '@/assets/icons'
 import { EmptyFallback } from '@/components/EmptyFallback'
 import { Text } from '@/components/Text'
 import { Button } from '@/components/_buttons/Button'
-import { absoluteRoutes } from '@/config/routes'
 import { useHeadTags } from '@/hooks/useHeadTags'
 import { useUploadsStore } from '@/providers/uploads/uploads.store'
 import { AssetUpload } from '@/providers/uploads/uploads.types'
 import { useUser } from '@/providers/user/user.hooks'
+import { useVideoWorkspace } from '@/providers/videoWorkspace'
 import { arrayFrom } from '@/utils/data'
 
 import { UploadsContainer } from './MyUploadsView.styles'
@@ -24,6 +24,7 @@ export const MyUploadsView: FC = () => {
   const { channelId } = useUser()
 
   const headTags = useHeadTags('My uploads')
+  const { uploadVideoButtonProps } = useVideoWorkspace()
 
   const channelUploads = useUploadsStore((state) => state.uploads.filter((asset) => asset.owner === channelId), shallow)
   const isSyncing = useUploadsStore((state) => state.isSyncing)
@@ -62,12 +63,7 @@ export const MyUploadsView: FC = () => {
           title="No ongoing uploads"
           subtitle="You will see status of each ongoing upload here."
           button={
-            <Button
-              icon={<SvgActionUpload />}
-              variant="secondary"
-              size="large"
-              to={absoluteRoutes.studio.videoWorkspace()}
-            >
+            <Button icon={<SvgActionUpload />} variant="secondary" size="large" {...uploadVideoButtonProps}>
               Upload video
             </Button>
           }

+ 10 - 32
packages/atlas/src/views/studio/MyVideosView/MyVideosView.tsx

@@ -1,5 +1,5 @@
 import axios from 'axios'
-import { useCallback, useEffect, useRef, useState } from 'react'
+import { useEffect, useRef, useState } from 'react'
 import { useQuery } from 'react-query'
 import { useNavigate } from 'react-router-dom'
 
@@ -58,7 +58,7 @@ export const MyVideosView = () => {
   const headTags = useHeadTags('My videos')
   const navigate = useNavigate()
   const { channelId } = useAuthorizedUser()
-  const { editedVideoInfo, setEditedVideo } = useVideoWorkspace()
+  const { editedVideoInfo, setEditedVideo, uploadVideoButtonProps } = useVideoWorkspace()
   const { displaySnackbar, updateSnackbar } = useSnackbar()
   const [videosPerRow, setVideosPerRow] = useState(INITIAL_VIDEOS_PER_ROW)
   const [sortVideosBy, setSortVideosBy] = useState<VideoOrderByInput>(VideoOrderByInput.CreatedAtDesc)
@@ -181,8 +181,6 @@ export const MyVideosView = () => {
     }
   }
 
-  const handleAddVideoTab = useCallback(() => setEditedVideo(), [setEditedVideo])
-
   type HandleVideoClickOpts = {
     draft?: boolean
     minimized?: boolean
@@ -265,7 +263,9 @@ export const MyVideosView = () => {
         .slice(videosPerPage * currentPage, currentPage * videosPerPage + videosPerPage)
         .map((draft, idx) => {
           if (draft === 'new-video-tile') {
-            return <NewVideoTile loading={areTilesLoading} key={`$draft-${idx}`} onClick={handleAddVideoTab} />
+            return (
+              <NewVideoTile loading={areTilesLoading} key={`$draft-${idx}`} onClick={uploadVideoButtonProps.onClick} />
+            )
           }
           return (
             <VideoTileDraft
@@ -282,7 +282,7 @@ export const MyVideosView = () => {
             <NewVideoTile
               loading={video === 'new-video-tile' ? areTilesLoading : true}
               key={idx}
-              onClick={video === 'new-video-tile' ? handleAddVideoTab : undefined}
+              onClick={video === 'new-video-tile' ? uploadVideoButtonProps.onClick : undefined}
             />
           )
         }
@@ -333,13 +333,7 @@ export const MyVideosView = () => {
         My videos
       </Text>
       {!smMatch && sortVisibleAndUploadButtonVisible && (
-        <MobileButton
-          size="large"
-          to={absoluteRoutes.studio.videoWorkspace()}
-          icon={<SvgActionAddVideo />}
-          onClick={handleAddVideoTab}
-          fullWidth
-        >
+        <MobileButton size="large" icon={<SvgActionAddVideo />} fullWidth {...uploadVideoButtonProps}>
           Upload video
         </MobileButton>
       )}
@@ -349,13 +343,7 @@ export const MyVideosView = () => {
           title="Add your first video"
           subtitle="No videos uploaded yet. Start publishing by adding your first video to Joystream."
           button={
-            <Button
-              icon={<SvgActionUpload />}
-              to={absoluteRoutes.studio.videoWorkspace()}
-              variant="secondary"
-              size="large"
-              onClick={handleAddVideoTab}
-            >
+            <Button icon={<SvgActionUpload />} variant="secondary" size="large" {...uploadVideoButtonProps}>
               Upload video
             </Button>
           }
@@ -366,11 +354,7 @@ export const MyVideosView = () => {
             <Tabs initialIndex={0} tabs={mappedTabs} onSelectTab={handleSetCurrentTab} />
             {mdMatch && sortVisibleAndUploadButtonVisible && sortSelectNode}
             {smMatch && sortVisibleAndUploadButtonVisible && (
-              <Button
-                to={absoluteRoutes.studio.videoWorkspace()}
-                icon={<SvgActionAddVideo />}
-                onClick={handleAddVideoTab}
-              >
+              <Button {...uploadVideoButtonProps} icon={<SvgActionAddVideo />}>
                 Upload video
               </Button>
             )}
@@ -434,13 +418,7 @@ export const MyVideosView = () => {
                   : 'Videos published with "Unlisted" privacy setting will show up here.'
               }
               button={
-                <Button
-                  icon={<SvgActionUpload />}
-                  to={absoluteRoutes.studio.videoWorkspace()}
-                  variant="secondary"
-                  size="large"
-                  onClick={handleAddVideoTab}
-                >
+                <Button icon={<SvgActionUpload />} variant="secondary" size="large" {...uploadVideoButtonProps}>
                   Upload video
                 </Button>
               }