Skip to content

Commit

Permalink
Add pubsub to sync preview and content cards
Browse files Browse the repository at this point in the history
  • Loading branch information
adityapawar1 committed Apr 18, 2024
1 parent 9bc23e7 commit 47b3c11
Show file tree
Hide file tree
Showing 10 changed files with 202 additions and 59 deletions.
9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@
"react-native-vector-icons": "^10.0.2",
"react-scroll-to-top": "^3.0.0",
"use-debounce": "^10.0.0",
"validator": "^13.11.0"
"validator": "^13.11.0",
"expo-image": "~1.3.5"
},
"devDependencies": {
"@babel/core": "^7.20.0",
Expand Down
13 changes: 8 additions & 5 deletions src/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@ import { Stack } from 'expo-router';
import { SafeAreaProvider } from 'react-native-safe-area-context';

import { AuthContextProvider } from '../utils/AuthContext';
import { BooleanPubSubProvider } from '../utils/PubSubContext';
import ToastComponent from '../components/Toast/Toast';
import { Keyboard, TouchableWithoutFeedback } from 'react-native';

function StackLayout() {
return (
<SafeAreaProvider>
<AuthContextProvider>
<Stack>
<Stack.Screen name="index" options={{ headerShown: false }} />
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="auth" options={{ headerShown: false }} />
</Stack>
<BooleanPubSubProvider>
<Stack>
<Stack.Screen name="index" options={{ headerShown: false }} />
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="auth" options={{ headerShown: false }} />
</Stack>
</BooleanPubSubProvider>
</AuthContextProvider>
<ToastComponent />
</SafeAreaProvider>
Expand Down
4 changes: 1 addition & 3 deletions src/components/AuthorCard/AuthorCard.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { Image, Pressable, Text, View } from 'react-native';

import { Image, Text, View } from 'react-native';
import styles from './styles';
import globalStyles from '../../styles/globalStyles';

type AuthorCardProps = {
name: string;
Expand Down
63 changes: 47 additions & 16 deletions src/components/ContentCard/ContentCard.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import {
GestureResponderEvent,
Image,
Pressable,
Text,
View,
TouchableOpacity,
} from 'react-native';
import { Image } from 'expo-image';

import styles from './styles';
import { addUserStoryToReadingList, deleteUserStoryToReadingList, isStoryInReadingList } from '../../queries/savedStories';
import {
addUserStoryToReadingList,
deleteUserStoryToReadingList,
isStoryInReadingList,
} from '../../queries/savedStories';
import globalStyles from '../../styles/globalStyles';
import { useSession } from '../../utils/AuthContext';
import Emoji from 'react-native-emoji';
import { useEffect, useMemo, useState } from 'react';

import { useCallback, useEffect, useMemo, useState } from 'react';
import { usePubSub } from '../../utils/PubSubContext';

type ContentCardProps = {
title: string;
Expand All @@ -37,19 +41,47 @@ function ContentCard({
}: ContentCardProps) {
const { user } = useSession();
const [storyIsSaved, setStoryIsSaved] = useState(false);
const { channels, initializeChannel, publish } = usePubSub();

const savedStoryImageComponent = useMemo(() => {
return (
<Image
style={{ width: 30, height: 30 }}
source={savedStoryImage}
/>
)
}, [])
const saveStoryImageComponent = useMemo(() => {
return (
<Image
style={{ width: 30, height: 30 }}
source={saveStoryImage}
/>
)
}, [])

useEffect(() => {
isStoryInReadingList(storyId, user?.id).then(storyInReadingList => setStoryIsSaved(storyInReadingList))
}, [storyId])
isStoryInReadingList(storyId, user?.id).then(storyInReadingList => {
setStoryIsSaved(storyInReadingList)
initializeChannel(storyId);
});
}, [storyId]);

useEffect(() => {
// if another card updates this story, update it here also
if (typeof channels[storyId] !== "undefined") {
setStoryIsSaved(channels[storyId]);
}
}, [channels[storyId]]);

const saveStory = () => {
if (storyIsSaved) {
deleteUserStoryToReadingList(user?.id, storyId);
const saveStory = async (saved: boolean) => {
setStoryIsSaved(saved);
publish(storyId, saved);
if (saved) {
await addUserStoryToReadingList(user?.id, storyId);
} else {
addUserStoryToReadingList(user?.id, storyId);
await deleteUserStoryToReadingList(user?.id, storyId);
}
setStoryIsSaved(!storyIsSaved);
};

return (
Expand Down Expand Up @@ -98,11 +130,10 @@ function ContentCard({
</Text>
</View>
</View>
<TouchableOpacity onPress={() => saveStory()}>
<Image
style={styles.saveStoryImage}
source={storyIsSaved ? savedStoryImage : saveStoryImage}
/>
<TouchableOpacity onPress={() => saveStory(!storyIsSaved)}>
{storyIsSaved ?
savedStoryImageComponent : saveStoryImageComponent
}
</TouchableOpacity>
</View>
</View>
Expand Down
69 changes: 54 additions & 15 deletions src/components/PreviewCard/PreviewCard.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import * as cheerio from 'cheerio';
import {
GestureResponderEvent,
Image,
Pressable,
Text,
TouchableOpacity,
View,
} from 'react-native';
import Emoji from 'react-native-emoji';
import { Image } from 'expo-image';

import styles from './styles';
import globalStyles from '../../styles/globalStyles';
import { useEffect, useState } from 'react';
import { addUserStoryToReadingList, deleteUserStoryToReadingList, isStoryInReadingList } from '../../queries/savedStories';
import { useCallback, useEffect, useMemo, useState } from 'react';
import {
addUserStoryToReadingList,
deleteUserStoryToReadingList,
isStoryInReadingList,
} from '../../queries/savedStories';
import { useSession } from '../../utils/AuthContext';
import { useIsFocused } from '@react-navigation/native';
import { usePubSub } from '../../utils/PubSubContext';

const placeholderImage =
'https://gwn-uploads.s3.amazonaws.com/wp-content/uploads/2021/10/10120952/Girls-Write-Now-logo-avatar.png';
Expand Down Expand Up @@ -45,19 +50,54 @@ function PreviewCard({
const { user } = useSession();
const isFocused = useIsFocused();
const [storyIsSaved, setStoryIsSaved] = useState(false);
const { channels, initializeChannel, publish } = usePubSub();

const savedStoryImageComponent = useMemo(() => {
return (
<Image
style={{ width: 30, height: 30 }}
source={savedStoryImage}
/>
)
}, [])
const saveStoryImageComponent = useMemo(() => {
return (
<Image
style={{ width: 30, height: 30 }}
source={saveStoryImage}
/>
)
}, [])

useEffect(() => {
isStoryInReadingList(storyId, user?.id).then(storyInReadingList => setStoryIsSaved(storyInReadingList))
}, [storyId, isFocused])
isStoryInReadingList(storyId, user?.id).then(storyInReadingList => {
setStoryIsSaved(storyInReadingList)
initializeChannel(storyId);
});
}, [storyId]);

useEffect(() => {
// if another card updates this story, update it here also
if (typeof channels[storyId] !== "undefined") {
setStoryIsSaved(channels[storyId])
}
}, [channels[storyId]]);


useEffect(() => {
isStoryInReadingList(storyId, user?.id).then(storyInReadingList =>
setStoryIsSaved(storyInReadingList),
);
}, [storyId, isFocused]);

const saveStory = () => {
if (storyIsSaved) {
deleteUserStoryToReadingList(user?.id, storyId);
const saveStory = async (saved: boolean) => {
setStoryIsSaved(saved);
publish(storyId, saved);
if (saved) {
await addUserStoryToReadingList(user?.id, storyId);
} else {
addUserStoryToReadingList(user?.id, storyId);
await deleteUserStoryToReadingList(user?.id, storyId);
}
setStoryIsSaved(!storyIsSaved);
};

return (
Expand All @@ -67,11 +107,10 @@ function PreviewCard({
<Text numberOfLines={1} style={[globalStyles.h3, styles.title]}>
{title}
</Text>
<TouchableOpacity onPress={() => saveStory()}>
<Image
style={{ width: 30, height: 30 }}
source={storyIsSaved ? savedStoryImage : saveStoryImage}
/>
<TouchableOpacity onPress={() => saveStory(!storyIsSaved)}>
{storyIsSaved ?
savedStoryImageComponent : saveStoryImageComponent
}
</TouchableOpacity>
</View>
<View style={styles.body}>
Expand Down
2 changes: 2 additions & 0 deletions src/components/PreviewCard/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,11 @@ const styles = StyleSheet.create({
paddingTop: 16,
paddingLeft: 12,
paddingRight: 12,
paddingBottom: 8,
borderBottomColor: '#EBEBEB',
borderBottomWidth: StyleSheet.hairlineWidth,
flexDirection: 'row',
flexGrow: 1,
justifyContent: 'space-between',
},
tag: {
Expand Down
26 changes: 13 additions & 13 deletions src/queries/savedStories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ import supabase from '../utils/supabase';

enum SavedList {
FAVORITES = 'favorites',
READING_LIST = 'reading list'
READING_LIST = 'reading list',
}


async function fetchUserStories(
user_id: string | undefined,
name: string | undefined,
Expand Down Expand Up @@ -65,13 +64,13 @@ async function addUserStory(
) {
const { error } = await supabase
.from('saved_stories')
.insert([{ user_id: user_id, story_id: story_id, name: name }])
.upsert([{ user_id: user_id, story_id: story_id, name: name }])
.select();

if (error) {
if (process.env.NODE_ENV !== 'production') {
throw new Error(
`An error occured when trying to set user saved stories: ${error.details}`,
`An error occured when trying to set user saved stories: ${JSON.stringify(error)}`,
);
}
}
Expand Down Expand Up @@ -105,7 +104,6 @@ export async function deleteUserStoryToReadingList(
deleteUserStory(user_id, story_id, SavedList.READING_LIST);
}


export async function deleteUserStory(
user_id: string | undefined,
story_id: number,
Expand All @@ -127,16 +125,18 @@ export async function deleteUserStory(
}
}

export async function isStoryInReadingList(storyId: number, userId: string | undefined): Promise<boolean> {
let { data, error } = await supabase
.rpc('is_story_saved_for_user', {
list_name: "reading list",
story_db_id: storyId,
user_uuid: userId,
})
export async function isStoryInReadingList(
storyId: number,
userId: string | undefined,
): Promise<boolean> {
let { data, error } = await supabase.rpc('is_story_saved_for_user', {
list_name: 'reading list',
story_db_id: storyId,
user_uuid: userId,
});

if (error) {
console.error(error)
console.error(error);
return false;
}

Expand Down
12 changes: 6 additions & 6 deletions src/utils/AuthContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ export interface AuthState {
resendVerification: (email: string) => Promise<AuthResponse>;
resetPassword: (email: string) => Promise<
| {
data: object;
error: null;
}
data: object;
error: null;
}
| {
data: null;
error: AuthError;
}
data: null;
error: AuthError;
}
>;
updateUser: (attributes: UserAttributes) => Promise<UserResponse>;
signOut: () => Promise<void>;
Expand Down
Loading

0 comments on commit 47b3c11

Please sign in to comment.