diff --git a/apps/mobile/src/components/Gallery/DeleteGalleryBottomSheet.tsx b/apps/mobile/src/components/Gallery/DeleteGalleryBottomSheet.tsx
new file mode 100644
index 0000000000..3458435190
--- /dev/null
+++ b/apps/mobile/src/components/Gallery/DeleteGalleryBottomSheet.tsx
@@ -0,0 +1,73 @@
+import { useCallback } from 'react';
+import { View } from 'react-native';
+import { graphql, useFragment } from 'react-relay';
+import useDeleteGallery from 'shared/hooks/useDeleteGallery';
+
+import { Button } from '~/components/Button';
+import { Typography } from '~/components/Typography';
+import { useBottomSheetModalActions } from '~/contexts/BottomSheetModalContext';
+import { DeleteGalleryBottomSheet$key } from '~/generated/DeleteGalleryBottomSheet.graphql';
+import { contexts } from '~/shared/analytics/constants';
+
+type Props = {
+ galleryRef: DeleteGalleryBottomSheet$key;
+};
+
+export default function DeleteGalleryBottomSheet({ galleryRef }: Props) {
+ const gallery = useFragment(
+ graphql`
+ fragment DeleteGalleryBottomSheet on Gallery {
+ dbid
+ }
+ `,
+ galleryRef
+ );
+ const deleteGallery = useDeleteGallery();
+ const { hideBottomSheetModal } = useBottomSheetModalActions();
+
+ const handleBack = useCallback(() => {
+ hideBottomSheetModal();
+ }, [hideBottomSheetModal]);
+
+ const handleDelete = useCallback(() => {
+ deleteGallery(gallery.dbid);
+ hideBottomSheetModal();
+ }, [deleteGallery, gallery, hideBottomSheetModal]);
+
+ return (
+
+
+
+ Delete gallery
+
+
+ Are you sure you want to delete this gallery?
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/Gallery/PublishGallery/PublishGallery.tsx b/apps/mobile/src/components/Gallery/PublishGallery/PublishGallery.tsx
new file mode 100644
index 0000000000..f5908838f4
--- /dev/null
+++ b/apps/mobile/src/components/Gallery/PublishGallery/PublishGallery.tsx
@@ -0,0 +1,108 @@
+import { useNavigation } from '@react-navigation/native';
+import { useCallback } from 'react';
+import { TextInput, View } from 'react-native';
+import { graphql, useFragment } from 'react-relay';
+
+import { BackButton } from '~/components/BackButton';
+import { Button } from '~/components/Button';
+import { BaseM } from '~/components/Text';
+import { Typography } from '~/components/Typography';
+import { useBottomSheetModalActions } from '~/contexts/BottomSheetModalContext';
+import { useGalleryEditorActions } from '~/contexts/GalleryEditor/GalleryEditorContext';
+import { PublishGalleryFragment$key } from '~/generated/PublishGalleryFragment.graphql';
+import { RootStackNavigatorProp } from '~/navigation/types';
+
+import { PublishGalleryBottomSheet } from './PublishGalleryBottomSheet';
+import { PublishGalleryPreview } from './PublishGalleryPreview';
+
+type Props = {
+ galleryRef: PublishGalleryFragment$key;
+};
+
+export function PublishGallery({ galleryRef }: Props) {
+ const gallery = useFragment(
+ graphql`
+ fragment PublishGalleryFragment on Gallery {
+ ...PublishGalleryPreviewFragment
+ }
+ `,
+ galleryRef
+ );
+
+ const { galleryName, setGalleryName, galleryDescription, setGalleryDescription, saveGallery } =
+ useGalleryEditorActions();
+ const { showBottomSheetModal, hideBottomSheetModal } = useBottomSheetModalActions();
+ const navigation = useNavigation();
+
+ const handleSaveGallery = useCallback(async () => {
+ hideBottomSheetModal();
+ await saveGallery();
+ navigation.navigate('MainTabs', {
+ screen: 'HomeTab',
+ params: { screen: 'Home', params: { screen: 'For You', params: {} } },
+ });
+ }, [hideBottomSheetModal, navigation, saveGallery]);
+
+ const handlePublish = useCallback(() => {
+ showBottomSheetModal({
+ content: ,
+ });
+ }, [handleSaveGallery, showBottomSheetModal]);
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+ {galleryName}
+
+
+
+
+
+
+
+
+ {galleryDescription}
+
+
+
+ >
+ );
+}
diff --git a/apps/mobile/src/components/Gallery/PublishGallery/PublishGalleryBottomSheet.tsx b/apps/mobile/src/components/Gallery/PublishGallery/PublishGalleryBottomSheet.tsx
new file mode 100644
index 0000000000..aa383a2206
--- /dev/null
+++ b/apps/mobile/src/components/Gallery/PublishGallery/PublishGalleryBottomSheet.tsx
@@ -0,0 +1,98 @@
+import { BottomSheetTextInput } from '@gorhom/bottom-sheet';
+import clsx from 'clsx';
+import { useColorScheme } from 'nativewind';
+import { useCallback, useState } from 'react';
+import { KeyboardAvoidingView, View } from 'react-native';
+import { contexts } from 'shared/analytics/constants';
+import colors from 'shared/theme/colors';
+
+import { Button } from '~/components/Button';
+import { Typography } from '~/components/Typography';
+import { useToastActions } from '~/contexts/ToastContext';
+
+type Props = {
+ onPublish: () => void;
+};
+
+export function PublishGalleryBottomSheet({ onPublish }: Props) {
+ const { colorScheme } = useColorScheme();
+ const [isInputFocused, setIsInputFocused] = useState(false);
+ const { pushToast } = useToastActions();
+
+ const handlePostAndPublish = useCallback(() => {
+ // handle post and publish
+ pushToast({
+ message: 'Feature in progress',
+ });
+ }, [pushToast]);
+
+ return (
+
+
+
+
+ Publish
+
+
+ Share your Gallery in a post to showcase your collection to collectors and creators.
+
+
+
+ setIsInputFocused(true)}
+ onBlur={() => setIsInputFocused(false)}
+ placeholder="Add an optional caption"
+ />
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/Gallery/PublishGallery/PublishGalleryPreview.tsx b/apps/mobile/src/components/Gallery/PublishGallery/PublishGalleryPreview.tsx
new file mode 100644
index 0000000000..57c4d82909
--- /dev/null
+++ b/apps/mobile/src/components/Gallery/PublishGallery/PublishGalleryPreview.tsx
@@ -0,0 +1,134 @@
+import { useMemo } from 'react';
+import { View } from 'react-native';
+import { graphql, useFragment } from 'react-relay';
+import { removeNullValues } from 'shared/relay/removeNullValues';
+import { parseCollectionLayout } from 'shared/utils/collectionLayout';
+
+import { GalleryEditorTokenPreview } from '~/components/GalleryEditor/GalleryEditorTokenPreview';
+import { WhiteSpace } from '~/components/GalleryEditor/SortableTokenGrid/SortableTokenGrid';
+import { Typography } from '~/components/Typography';
+import { PublishGalleryPreviewFragment$key } from '~/generated/PublishGalleryPreviewFragment.graphql';
+
+type Props = {
+ galleryRef: PublishGalleryPreviewFragment$key;
+};
+
+const CONTAINER_WIDTH = 300;
+
+export function PublishGalleryPreview({ galleryRef }: Props) {
+ const gallery = useFragment(
+ graphql`
+ fragment PublishGalleryPreviewFragment on Gallery {
+ id
+ name
+
+ collections {
+ name
+ collectorsNote
+ layout {
+ __typename
+ sections
+ sectionLayout {
+ columns
+ whitespace
+ }
+ ...collectionLayoutParseFragment
+ }
+ tokens {
+ id
+ token {
+ ...GalleryEditorTokenPreviewFragment
+ }
+ }
+ }
+ }
+ `,
+ galleryRef
+ );
+
+ // get first section in the gallery
+ const firstSection = gallery.collections?.[0];
+
+ const nonNullTokens = useMemo(() => {
+ return removeNullValues(firstSection?.tokens);
+ }, [firstSection?.tokens]);
+
+ const rows = useMemo(() => {
+ const layout = firstSection?.layout || {
+ __typename: 'CollectionLayout',
+ sections: [],
+ sectionLayout: [],
+ };
+
+ return parseCollectionLayout(
+ nonNullTokens,
+ {
+ ...layout,
+ sections: removeNullValues(layout.sections ?? []),
+ sectionLayout: removeNullValues(layout.sectionLayout ?? []),
+ },
+ true
+ );
+ }, [nonNullTokens, firstSection?.layout]);
+
+ if (!gallery) {
+ return null;
+ }
+
+ return (
+
+
+
+
+ {firstSection?.name || 'Untitled'}
+
+
+ {firstSection?.collectorsNote}
+
+
+
+
+
+ {rows.map((row) => {
+ // 64 is the padding on the left and right of the container
+ const widthPerToken = (CONTAINER_WIDTH - 64 - 16) / row.columns;
+
+ return (
+
+ {row.items.map((item) => {
+ if ('whitespace' in item) {
+ return ;
+ } else {
+ return (
+
+ {item.token && }
+
+ );
+ }
+ })}
+
+ );
+ })}
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/GalleryEditor/GalleryEditorHeader.tsx b/apps/mobile/src/components/GalleryEditor/GalleryEditorHeader.tsx
index b5a2bea828..e53202d93e 100644
--- a/apps/mobile/src/components/GalleryEditor/GalleryEditorHeader.tsx
+++ b/apps/mobile/src/components/GalleryEditor/GalleryEditorHeader.tsx
@@ -15,13 +15,18 @@ export function GalleryEditorHeader() {
className="text-[32px] leading-[36px] text-metal dark:text-white"
onChangeText={setGalleryName}
placeholder="My Gallery"
+ style={{
+ fontFamily: 'GTAlpinaLight',
+ }}
+ autoCorrect={false}
+ spellCheck={false}
>
{galleryName}
diff --git a/apps/mobile/src/components/GalleryEditor/GalleryEditorNavbar.tsx b/apps/mobile/src/components/GalleryEditor/GalleryEditorNavbar.tsx
index dd7ebd3681..b39936bdad 100644
--- a/apps/mobile/src/components/GalleryEditor/GalleryEditorNavbar.tsx
+++ b/apps/mobile/src/components/GalleryEditor/GalleryEditorNavbar.tsx
@@ -1,3 +1,4 @@
+import { useNavigation } from '@react-navigation/native';
import clsx from 'clsx';
import { useCallback } from 'react';
import { View } from 'react-native';
@@ -5,13 +6,16 @@ import { View } from 'react-native';
import { useBottomSheetModalActions } from '~/contexts/BottomSheetModalContext';
import { useGalleryEditorActions } from '~/contexts/GalleryEditor/GalleryEditorContext';
import { StagedSectionList } from '~/contexts/GalleryEditor/types';
+import { RootStackNavigatorProp } from '~/navigation/types';
import { BackButton } from '../BackButton';
import { Button } from '../Button';
import { BaseM } from '../Text';
export function GalleryEditorNavbar() {
- const { activeRowId, sections, sectionIdBeingEdited, saveGallery } = useGalleryEditorActions();
+ const { activeRowId, galleryId, sections, sectionIdBeingEdited, saveGallery } =
+ useGalleryEditorActions();
+ const navigation = useNavigation();
const { showBottomSheetModal } = useBottomSheetModalActions();
@@ -27,6 +31,14 @@ export function GalleryEditorNavbar() {
});
}, [activeRowId, sections, sectionIdBeingEdited, showBottomSheetModal]);
+ const handlePublishGallery = useCallback(async () => {
+ await saveGallery();
+
+ navigation.navigate('PublishGallery', {
+ galleryId,
+ });
+ }, [galleryId, navigation, saveGallery]);
+
return (
@@ -43,7 +55,7 @@ export function GalleryEditorNavbar() {
variant="secondary"
/>
);
}
-
-type GalleryPreviewCardBottomSheetProps = {
- galleryId: string;
- onClose: () => void;
-};
-
-function GalleryPreviewCardBottomSheet({ galleryId, onClose }: GalleryPreviewCardBottomSheetProps) {
- const { bottom } = useSafeAreaPadding();
- const { pushToast } = useToastActions();
- const navigation = useNavigation();
-
- const handleInProgress = useCallback(() => {
- pushToast({
- message: 'Feature in progress',
- });
- }, [pushToast]);
-
- const handleEditGallery = useCallback(() => {
- navigation.navigate('GalleryEditor', {
- galleryId,
- stagedTokens: [],
- });
- onClose();
- }, [galleryId, navigation, onClose]);
-
- return (
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/apps/mobile/src/components/ProfileView/GalleryPreviewCardBottomSheet.tsx b/apps/mobile/src/components/ProfileView/GalleryPreviewCardBottomSheet.tsx
new file mode 100644
index 0000000000..6dfdfe7df0
--- /dev/null
+++ b/apps/mobile/src/components/ProfileView/GalleryPreviewCardBottomSheet.tsx
@@ -0,0 +1,92 @@
+import { useNavigation } from '@react-navigation/native';
+import { useCallback } from 'react';
+import { View } from 'react-native';
+import { graphql, useFragment } from 'react-relay';
+import { contexts } from 'shared/analytics/constants';
+
+import { useBottomSheetModalActions } from '~/contexts/BottomSheetModalContext';
+import { useToastActions } from '~/contexts/ToastContext';
+import { GalleryPreviewCardBottomSheetFragment$key } from '~/generated/GalleryPreviewCardBottomSheetFragment.graphql';
+import { RootStackNavigatorProp } from '~/navigation/types';
+
+import { BottomSheetRow } from '../BottomSheetRow';
+import DeleteGalleryBottomSheet from '../Gallery/DeleteGalleryBottomSheet';
+import { useSafeAreaPadding } from '../SafeAreaViewWithPadding';
+
+type Props = {
+ galleryRef: GalleryPreviewCardBottomSheetFragment$key;
+ onClose: () => void;
+};
+
+export function GalleryPreviewCardBottomSheet({ galleryRef, onClose }: Props) {
+ const gallery = useFragment(
+ graphql`
+ fragment GalleryPreviewCardBottomSheetFragment on Gallery {
+ dbid
+ ...DeleteGalleryBottomSheet
+ }
+ `,
+ galleryRef
+ );
+
+ const galleryId = gallery?.dbid;
+
+ const { bottom } = useSafeAreaPadding();
+ const { pushToast } = useToastActions();
+
+ const { showBottomSheetModal } = useBottomSheetModalActions();
+ const navigation = useNavigation();
+
+ const handleInProgress = useCallback(() => {
+ pushToast({
+ message: 'Feature in progress',
+ });
+ }, [pushToast]);
+
+ const handleEditGallery = useCallback(() => {
+ navigation.navigate('GalleryEditor', {
+ galleryId,
+ stagedTokens: [],
+ });
+ onClose();
+ }, [galleryId, navigation, onClose]);
+
+ const handleDeleteGallery = useCallback(() => {
+ showBottomSheetModal({
+ content: ,
+ });
+ }, [gallery, showBottomSheetModal]);
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/ProfileView/Tabs/ProfileViewGalleriesTab.tsx b/apps/mobile/src/components/ProfileView/Tabs/ProfileViewGalleriesTab.tsx
index 262d44e7d1..c9016e42f7 100644
--- a/apps/mobile/src/components/ProfileView/Tabs/ProfileViewGalleriesTab.tsx
+++ b/apps/mobile/src/components/ProfileView/Tabs/ProfileViewGalleriesTab.tsx
@@ -1,14 +1,21 @@
+import { useNavigation } from '@react-navigation/native';
import { ListRenderItem } from '@shopify/flash-list';
import { useCallback, useMemo } from 'react';
import { View } from 'react-native';
import { Tabs } from 'react-native-collapsible-tab-view';
import { useFragment } from 'react-relay';
-import { graphql } from 'relay-runtime';
+import { graphql, SelectorStoreUpdater } from 'relay-runtime';
+import isFeatureEnabled, { FeatureFlag } from 'src/utils/isFeatureEnabled';
+import { Button } from '~/components/Button';
import { GalleryPreviewCard } from '~/components/ProfileView/GalleryPreviewCard';
import { useListContentStyle } from '~/components/ProfileView/Tabs/useListContentStyle';
+import { useToastActions } from '~/contexts/ToastContext';
import { GalleryPreviewCardFragment$key } from '~/generated/GalleryPreviewCardFragment.graphql';
import { ProfileViewGalleriesTabFragment$key } from '~/generated/ProfileViewGalleriesTabFragment.graphql';
+import { useCreateGalleryMutation } from '~/generated/useCreateGalleryMutation.graphql';
+import { RootStackNavigatorProp } from '~/navigation/types';
+import useCreateGallery from '~/shared/hooks/useCreateGallery';
import { removeNullValues } from '~/shared/relay/removeNullValues';
type ListItem = {
@@ -40,23 +47,51 @@ export function ProfileViewGalleriesTab({ queryRef }: ProfileViewGalleriesTabPro
}
}
...GalleryPreviewCardQueryFragment
+ ...isFeatureEnabledFragment
}
`,
queryRef
);
+ const { pushToast } = useToastActions();
const user = query.userByUsername;
+ const createGallery = useCreateGallery();
+ const navigation = useNavigation();
+ const isGalleryEditorEnabled = isFeatureEnabled(FeatureFlag.GALLERY_EDITOR, query);
+
+ const handleCreateGallery = useCallback(async () => {
+ const latestPosition = query?.userByUsername?.galleries?.length.toString() ?? '0';
+
+ try {
+ await createGallery(
+ latestPosition,
+ (galleryId) => {
+ navigation.navigate('NftSelectorGalleryEditor', { galleryId, isNewGallery: true });
+ },
+ updater
+ );
+ } catch (error) {
+ if (error instanceof Error) {
+ pushToast({
+ message: 'Unfortunately there was an error to create your gallery',
+ });
+ }
+ }
+ }, [createGallery, navigation, pushToast, query?.userByUsername?.galleries?.length]);
const items = useMemo(() => {
- return removeNullValues(user?.galleries).map((gallery): ListItem => {
- return {
- kind: 'gallery',
+ const items: ListItem[] = [];
+ removeNullValues(user?.galleries).forEach((gallery) => {
+ items.push({
+ kind: 'gallery',
gallery,
galleryId: gallery.dbid,
isFeatured: user?.featuredGallery?.dbid === gallery.dbid,
- };
+ });
});
+
+ return items;
}, [user?.featuredGallery?.dbid, user?.galleries]);
const renderItem = useCallback>(
@@ -79,6 +114,54 @@ export function ProfileViewGalleriesTab({ queryRef }: ProfileViewGalleriesTabPro
return (
+ {isGalleryEditorEnabled && (
+
+
+
+ )}
);
}
+
+const updater: SelectorStoreUpdater = (store, response) => {
+ if (response.createGallery?.__typename === 'CreateGalleryPayload') {
+ const gallery = response.createGallery.gallery;
+
+ if (gallery) {
+ const galleryId = gallery.id;
+ const newGallery = store.get(galleryId);
+
+ if (!newGallery) {
+ return;
+ }
+
+ const userId = response.createGallery.gallery.owner?.id;
+
+ if (!userId) {
+ return;
+ }
+
+ const user = store.get(userId);
+
+ if (!user) {
+ return;
+ }
+
+ const galleries = user.getLinkedRecords('galleries');
+
+ if (!galleries) {
+ return;
+ }
+
+ const newGalleries = [...galleries, newGallery];
+
+ user.setLinkedRecords(newGalleries, 'galleries');
+ }
+ }
+};
diff --git a/apps/mobile/src/contexts/GalleryEditor/GalleryEditorContext.tsx b/apps/mobile/src/contexts/GalleryEditor/GalleryEditorContext.tsx
index 4557b5b5bb..71cd78d133 100644
--- a/apps/mobile/src/contexts/GalleryEditor/GalleryEditorContext.tsx
+++ b/apps/mobile/src/contexts/GalleryEditor/GalleryEditorContext.tsx
@@ -86,6 +86,13 @@ const GalleryEditorProvider = ({ children, queryRef }: Props) => {
dbid
# eslint-disable-next-line relay/must-colocate-fragment-spreads
...GalleryEditorTokenPreviewFragment
+ definition {
+ community {
+ dbid
+ name
+ description
+ }
+ }
}
}
}
@@ -101,8 +108,13 @@ const GalleryEditorProvider = ({ children, queryRef }: Props) => {
__typename
... on UpdateGalleryPayload {
gallery {
+ id
+ dbid
name
description
+ tokenPreviews {
+ medium
+ }
...getInitialCollectionsFromServerFragment
}
}
@@ -131,7 +143,9 @@ const GalleryEditorProvider = ({ children, queryRef }: Props) => {
getInitialCollectionsFromServer(gallery)
);
- const [sectionIdBeingEdited, setSectionIdBeingEdited] = useState(null);
+ const [sectionIdBeingEdited, setSectionIdBeingEdited] = useState(() => {
+ return sections[0]?.dbid ?? null;
+ });
const [deletedCollectionIds, setDeletedCollectionIds] = useState(() => {
return new Set();
@@ -334,14 +348,24 @@ const GalleryEditorProvider = ({ children, queryRef }: Props) => {
[updateRow]
);
+ type StagedToken = {
+ communityName: string;
+ communityDescription: string;
+ communityId: string;
+ } & StagedItem;
+
const toggleTokensStaged = useCallback(
(tokenIds: string[]) => {
- if (!activeRowId) {
- // Make a new row for the user
+ const rowId = activeRowId;
+
+ let sectionName = '';
+ let sectionCollectorsNote = '';
+
+ if (!rowId) {
return;
}
- const tokensToAdd: StagedItem[] = [];
+ const tokensToAdd: StagedToken[] = [];
tokenIds.forEach((tokenId) => {
const tokenRef = nonNullAllTokens.find((token) => token.dbid === tokenId);
@@ -353,15 +377,48 @@ const GalleryEditorProvider = ({ children, queryRef }: Props) => {
id: tokenId,
kind: 'token',
tokenRef,
+ communityName: tokenRef.definition.community?.name ?? '',
+ communityDescription: tokenRef.definition.community?.description ?? '',
+ communityId: tokenRef.definition.community?.dbid ?? '',
});
});
- updateRow(activeRowId, (previousRow) => {
+ // Check if all the tokensToAdd have the same community
+ const communityIds = new Set(tokensToAdd.map((token) => token.communityId));
+
+ // if there are only tokens from the same community, then the section name should be the community name
+ if (communityIds.size === 1 && tokensToAdd[0]) {
+ sectionName = tokensToAdd[0].communityName;
+ sectionCollectorsNote = tokensToAdd[0].communityDescription;
+ }
+
+ let isNewSection = false;
+
+ updateRow(rowId, (previousRow) => {
+ if (previousRow.items.length === 0) {
+ isNewSection = true;
+ // if the total number of tokens is less than 6, then the number of columns should be equal to the number of tokens
+ // if more than 6, then the number of columns should be equal to 6
+ const columns = tokensToAdd.length < 6 ? tokensToAdd.length : 6;
+ return { ...previousRow, items: tokensToAdd, columns };
+ }
+
const newItems = [...previousRow.items, ...tokensToAdd];
return { ...previousRow, items: newItems };
});
+
+ if (!sectionIdBeingEdited) {
+ return;
+ }
+
+ if (isNewSection) {
+ // Update the section with the new name and collectors note
+ updateSection(sectionIdBeingEdited, (previousSection) => {
+ return { ...previousSection, name: sectionName, collectorsNote: sectionCollectorsNote };
+ });
+ }
},
- [activeRowId, nonNullAllTokens, updateRow]
+ [activeRowId, nonNullAllTokens, sectionIdBeingEdited, updateRow, updateSection]
);
const reportError = useReportError();
diff --git a/apps/mobile/src/navigation/RootStackNavigator.tsx b/apps/mobile/src/navigation/RootStackNavigator.tsx
index ce81fdac0f..7df3238710 100644
--- a/apps/mobile/src/navigation/RootStackNavigator.tsx
+++ b/apps/mobile/src/navigation/RootStackNavigator.tsx
@@ -19,6 +19,7 @@ import { DesignSystemButtonsScreen } from '~/screens/DesignSystemButtonsScreen';
import { GalleryEditorNftSelector } from '~/screens/GalleryScreen/GalleryEditorNftSelector';
import { GalleryEditorNftSelectorContractScreen } from '~/screens/GalleryScreen/GalleryEditorNftSelectorContractScreen';
import { GalleryEditorScreen } from '~/screens/GalleryScreen/GalleryEditorScreen';
+import { PublishGalleryScreen } from '~/screens/GalleryScreen/PublishGallery/PublishGalleryScreen';
import { TwitterSuggestionListScreen } from '~/screens/HomeScreen/TwitterSuggestionListScreen';
import { UserSuggestionListScreen } from '~/screens/HomeScreen/UserSuggestionListScreen';
import { PostComposerScreen } from '~/screens/PostScreen/PostComposerScreen';
@@ -106,6 +107,7 @@ export function RootStackNavigator({ navigationContainerRef }: Props) {
+
{
diff --git a/apps/mobile/src/screens/GalleryScreen/GalleryEditorNftSelectorContractScreen.tsx b/apps/mobile/src/screens/GalleryScreen/GalleryEditorNftSelectorContractScreen.tsx
index 36694d255a..9ba6968f65 100644
--- a/apps/mobile/src/screens/GalleryScreen/GalleryEditorNftSelectorContractScreen.tsx
+++ b/apps/mobile/src/screens/GalleryScreen/GalleryEditorNftSelectorContractScreen.tsx
@@ -83,12 +83,13 @@ export function GalleryEditorNftSelectorContractScreen() {
params: {
galleryId: route.params.galleryId,
stagedTokens: [tokenId],
+ isNewGallery: route.params.isNewGallery,
},
merge: true,
});
}
},
- [navigation, isMultiselectMode, route.params.galleryId]
+ [navigation, isMultiselectMode, route.params.galleryId, route.params.isNewGallery]
);
const handleAddSelectedTokens = useCallback(() => {
@@ -99,10 +100,11 @@ export function GalleryEditorNftSelectorContractScreen() {
params: {
galleryId: route.params.galleryId,
stagedTokens: formattedTokens,
+ isNewGallery: route.params.isNewGallery,
},
merge: true,
});
- }, [navigation, route.params.galleryId, selectedTokens]);
+ }, [navigation, route.params.galleryId, selectedTokens, route.params.isNewGallery]);
const handleSelectedAllPress = useCallback(() => {
setSelectedTokens((prevTokens) => {
diff --git a/apps/mobile/src/screens/GalleryScreen/PublishGallery/PublishGalleryScreen.tsx b/apps/mobile/src/screens/GalleryScreen/PublishGallery/PublishGalleryScreen.tsx
new file mode 100644
index 0000000000..7f063016cd
--- /dev/null
+++ b/apps/mobile/src/screens/GalleryScreen/PublishGallery/PublishGalleryScreen.tsx
@@ -0,0 +1,65 @@
+import { RouteProp, useRoute } from '@react-navigation/native';
+import { Suspense } from 'react';
+import { KeyboardAvoidingView, View } from 'react-native';
+import { graphql, useLazyLoadQuery } from 'react-relay';
+
+import { PublishGallery } from '~/components/Gallery/PublishGallery/PublishGallery';
+import { useSafeAreaPadding } from '~/components/SafeAreaViewWithPadding';
+import GalleryEditorProvider from '~/contexts/GalleryEditor/GalleryEditorContext';
+import { PublishGalleryScreenQuery } from '~/generated/PublishGalleryScreenQuery.graphql';
+import { RootStackNavigatorParamList } from '~/navigation/types';
+
+function InnerPublishGalleryScreen() {
+ const route = useRoute>();
+
+ const query = useLazyLoadQuery(
+ graphql`
+ query PublishGalleryScreenQuery($galleryId: DBID!) {
+ viewer {
+ __typename
+ }
+ galleryById(id: $galleryId) {
+ __typename
+ ... on Gallery {
+ __typename
+ }
+ ...PublishGalleryFragment
+ }
+ ...GalleryEditorContextFragment
+ }
+ `,
+ {
+ galleryId: route.params.galleryId,
+ }
+ );
+
+ const { top } = useSafeAreaPadding();
+ const gallery = query.galleryById;
+
+ if (!gallery) {
+ throw new Error('Gallery not found');
+ }
+
+ return (
+
+
+
+
+
+ );
+}
+
+export function PublishGalleryScreen() {
+ return (
+
+
+
+
+
+ );
+}
diff --git a/apps/web/src/components/MultiGallery/DeleteGalleryConfirmation.tsx b/apps/web/src/components/MultiGallery/DeleteGalleryConfirmation.tsx
index 2750dacb9d..16c6bb2da9 100644
--- a/apps/web/src/components/MultiGallery/DeleteGalleryConfirmation.tsx
+++ b/apps/web/src/components/MultiGallery/DeleteGalleryConfirmation.tsx
@@ -4,11 +4,11 @@ import styled from 'styled-components';
import { useModalActions } from '~/contexts/modal/ModalContext';
import { useToastActions } from '~/contexts/toast/ToastContext';
import { contexts } from '~/shared/analytics/constants';
+import useDeleteGallery from '~/shared/hooks/useDeleteGallery';
import { Button } from '../core/Button/Button';
import { HStack } from '../core/Spacer/Stack';
import { BaseM } from '../core/Text/Text';
-import useDeleteGallery from './useDeleteGallery';
type Props = {
galleryId: string;
diff --git a/apps/web/src/contexts/globalLayout/GlobalNavbar/GalleryNavbar/GalleryRightContent.tsx b/apps/web/src/contexts/globalLayout/GlobalNavbar/GalleryNavbar/GalleryRightContent.tsx
index a44cc42011..4a23d6ed8d 100644
--- a/apps/web/src/contexts/globalLayout/GlobalNavbar/GalleryNavbar/GalleryRightContent.tsx
+++ b/apps/web/src/contexts/globalLayout/GlobalNavbar/GalleryNavbar/GalleryRightContent.tsx
@@ -13,7 +13,6 @@ import { DropdownSection } from '~/components/core/Dropdown/DropdownSection';
import IconContainer from '~/components/core/IconContainer';
import { HStack, VStack } from '~/components/core/Spacer/Stack';
import FollowButton from '~/components/Follow/FollowButton';
-import useCreateGallery from '~/components/MultiGallery/useCreateGallery';
import Settings from '~/components/Settings/Settings';
import { AuthButton } from '~/contexts/globalLayout/GlobalNavbar/AuthButton';
import { EditLink } from '~/contexts/globalLayout/GlobalNavbar/CollectionNavbar/EditLink';
@@ -27,6 +26,7 @@ import EditUserInfoModal from '~/scenes/UserGalleryPage/EditUserInfoModal';
import LinkButton from '~/scenes/UserGalleryPage/LinkButton';
import { contexts } from '~/shared/analytics/constants';
import { useTrack } from '~/shared/contexts/AnalyticsContext';
+import useCreateGallery from '~/shared/hooks/useCreateGallery';
import QRCodeButton from './QRCodeButton';
@@ -90,7 +90,13 @@ export function GalleryRightContent({ queryRef, galleryRef, username }: GalleryR
const latestPosition = query?.userByUsername?.galleries?.length.toString() ?? '0';
try {
- await createGallery(latestPosition);
+ await createGallery(latestPosition, (galleryId) => {
+ const route = {
+ pathname: '/gallery/[galleryId]/edit',
+ query: { galleryId },
+ };
+ router.push(route);
+ });
} catch (error) {
if (error instanceof Error) {
pushToast({
diff --git a/apps/web/src/components/MultiGallery/useCreateGallery.ts b/packages/shared/src/hooks/useCreateGallery.ts
similarity index 59%
rename from apps/web/src/components/MultiGallery/useCreateGallery.ts
rename to packages/shared/src/hooks/useCreateGallery.ts
index f77007aee0..26779bfd3c 100644
--- a/apps/web/src/components/MultiGallery/useCreateGallery.ts
+++ b/packages/shared/src/hooks/useCreateGallery.ts
@@ -1,21 +1,26 @@
-import { useRouter } from 'next/router';
import { useCallback } from 'react';
-import { graphql } from 'relay-runtime';
+import { graphql, SelectorStoreUpdater } from 'relay-runtime';
import { useCreateGalleryMutation } from '~/generated/useCreateGalleryMutation.graphql';
-import { ValidationError } from '~/shared/errors/ValidationError';
-import { usePromisifiedMutation } from '~/shared/relay/usePromisifiedMutation';
-export default function useCreateGallery() {
- const router = useRouter();
+import { ValidationError } from '../errors/ValidationError';
+import { usePromisifiedMutation } from '../relay/usePromisifiedMutation';
+export default function useCreateGallery() {
const [createGallery] = usePromisifiedMutation(graphql`
mutation useCreateGalleryMutation($input: CreateGalleryInput!) @raw_response_type {
createGallery(input: $input) {
... on CreateGalleryPayload {
+ __typename
gallery {
+ __typename
id
dbid
+ name
+ description
+ owner {
+ id
+ }
}
}
@@ -27,9 +32,14 @@ export default function useCreateGallery() {
`);
return useCallback(
- async (position: string) => {
+ async (
+ position: string,
+ onSuccess: (galleryId: string) => void,
+ updater?: SelectorStoreUpdater
+ ) => {
try {
const response = await createGallery({
+ updater,
variables: {
input: {
name: '',
@@ -43,18 +53,16 @@ export default function useCreateGallery() {
throw new ValidationError('The description you entered is too long.');
}
- const galleryId = response?.createGallery?.gallery?.dbid;
- const route = {
- pathname: '/gallery/[galleryId]/edit',
- query: { galleryId },
- };
- if (galleryId) {
- router.push(route);
+ if (
+ response.createGallery?.__typename === 'CreateGalleryPayload' &&
+ response.createGallery?.gallery?.dbid
+ ) {
+ onSuccess(response.createGallery.gallery.dbid);
}
} catch (error) {
throw new Error('Failed to create gallery');
}
},
- [createGallery, router]
+ [createGallery]
);
}
diff --git a/apps/web/src/components/MultiGallery/useDeleteGallery.ts b/packages/shared/src/hooks/useDeleteGallery.ts
similarity index 93%
rename from apps/web/src/components/MultiGallery/useDeleteGallery.ts
rename to packages/shared/src/hooks/useDeleteGallery.ts
index 93ec51ae5e..ed3794bd7f 100644
--- a/apps/web/src/components/MultiGallery/useDeleteGallery.ts
+++ b/packages/shared/src/hooks/useDeleteGallery.ts
@@ -2,7 +2,8 @@ import { useCallback } from 'react';
import { graphql } from 'relay-runtime';
import { useDeleteGalleryMutation } from '~/generated/useDeleteGalleryMutation.graphql';
-import { usePromisifiedMutation } from '~/shared/relay/usePromisifiedMutation';
+
+import { usePromisifiedMutation } from '../relay/usePromisifiedMutation';
export default function useDeleteGallery() {
const [deleteGallery] = usePromisifiedMutation(graphql`