Skip to content

Commit

Permalink
Merge branch 'main' of github.com:calblueprint/girls-write-now into a…
Browse files Browse the repository at this point in the history
…di/deployment
  • Loading branch information
adityapawar1 committed Jun 13, 2024
2 parents 3caf510 + 58e8ab2 commit bc8af1f
Show file tree
Hide file tree
Showing 38 changed files with 253 additions and 5 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,24 @@ This project is designed and developed by a team of UC Berkeley students through

Learn more about [Girls Write Now](https://girlswritenow.org/) and [Cal Blueprint](https://calblueprint.org/).

## App Preview

### Story Search/Filtering

https://github.com/calblueprint/girls-write-now/assets/34043950/6277c625-fa8a-495d-b2eb-ba70807cfd87

### Story Recommendation

![Simulator Screen Recording - iPhone 15 - 2024-04-25 at 14 09 19](https://github.com/calblueprint/girls-write-now/assets/34043950/6a92dbf7-be0e-4210-8a66-35fddd578fee)

### Saving/Favoriting Stories

![saved stories](https://github.com/calblueprint/girls-write-now/assets/34043950/e40b5a50-d53c-497a-8b70-433def6dde67)

### Reacting To Stories

![reactions](https://github.com/calblueprint/girls-write-now/assets/34043950/32d5c54e-5a4e-4517-8475-ea38b2e4cf69)

---

## Getting Started
Expand Down
5 changes: 5 additions & 0 deletions src/app/(tabs)/author/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ import {
import { Author, StoryPreview } from '../../../queries/types';
import globalStyles from '../../../styles/globalStyles';

/*
* This screen displays information about an author.
* When redirecting to this page, you must supply the parameter `{ author: string }`, which is the string of a number representing the id of the author.
* This id is parsed to get the integer id, used to fetch the author's data.
*/
function AuthorScreen() {
const [authorInfo, setAuthorInfo] = useState<Author>();
const [authorStoryPreview, setAuthorStoryPreview] =
Expand Down
6 changes: 6 additions & 0 deletions src/app/(tabs)/genre/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ import { StoryPreview, GenreStories } from '../../../queries/types';
import globalStyles from '../../../styles/globalStyles';
import { FilterDropdown } from '../../../components/FilterDropdown/FilterDropdown';

/*
* This screen allows the user to filter stories based on the tone and topic for a specific genre.
* When redirecting to this page, you must supply the parameters `{ genreId: string; genreType: GenreType; genreName: string }`
* genreType is a enum, which can either be a Parent or Child. Parent genre's are genre's with children. Ex. Filter is a parent of Children's Fiction.
* useEffects are triggered when a dropdown tone/topic is selected, and updates the screen with the correct filtered stories.
*/
function GenreScreen() {
const [genreStoryData, setGenreStoryData] = useState<GenreStories[]>();
const [genreStoryIds, setGenreStoryIds] = useState<string[]>([]);
Expand Down
5 changes: 5 additions & 0 deletions src/app/(tabs)/home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ import { StoryCard, StoryPreview } from '../../../queries/types';
import globalStyles from '../../../styles/globalStyles';
import { useSession } from '../../../utils/AuthContext';

/*
* This screen displays the home screen of the app.
* It displays the featured stories, recommended stories, and new stories. Note: recommended stories are only displayed if there are recent stories (that the user read, from the search screen)
* The featured stories header and description can be edited via the admin dashboard. Links in the description are automatically converted to hyperlinks
*/
function HomeScreen() {
const { user } = useSession();
const [username, setUsername] = useState('');
Expand Down
4 changes: 4 additions & 0 deletions src/app/(tabs)/library/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ import {
import { FlatList } from 'react-native-gesture-handler';
import { Channel, usePubSub } from '../../../utils/PubSubContext';

/*
* This screen displays the user's saved and favorited stories.
* The screen recieves updates from PreviewCard and ContentCard from the PubSubContext via the usePubSub hook. If a story is favorited, the channel will be updated, and a useEffect is triggered. The screen is updated after 4 seconds of the update. This is to give the user time to resave a story on the library page if they accidently unsave it.
*/
function LibraryScreen() {
const { user } = useSession();
const [favoritesSelected, setFavoritesSelected] = useState(true);
Expand Down
7 changes: 7 additions & 0 deletions src/app/(tabs)/search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ const setRecentStory = async (recentStories: StoryPreview[]) => {
}
};

/*
* This screen handles all the story searching. It initially loads all stories, and functions to filter the stories.
* When the user first lands on the page, or if the "cancel" button on the search bar is clicked, all the genres tiles are shown. Using the genre dropdown is equivilent to clicking on a genre tile in this state.
* When a genre tile is selected, the user is redirected to the genre screen.
* When the user starts using the search bar, the stories are filtered based on the search. The user can further filter down the search based on the genre/topic/tone dropdowns. All filters must match for a story to be shown. Only genres/topics/tones that return results based on ONLY the text search are shown in the dropdown. Selected genre/topic/tone dropdowns will not affect the options in the dropdowns, only changing the search text. Selecting an option changes the color of the dropdown.
* Stories are preloaded with reaction to reduce the amount of network requests sent to supabase.
*/
function SearchScreen() {
const [allStories, setAllStories] = useState<
StoryPreviewWithPreloadedReactions[]
Expand Down
72 changes: 71 additions & 1 deletion src/app/(tabs)/settings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
Platform,
Pressable,
Appearance,
TouchableOpacity,
} from 'react-native';
import { Icon } from 'react-native-elements';
import { ScrollView } from 'react-native-gesture-handler';
Expand All @@ -22,7 +23,13 @@ import colors from '../../../styles/colors';
import globalStyles from '../../../styles/globalStyles';
import { useSession } from '../../../utils/AuthContext';
import supabase from '../../../utils/supabase';
import { deleteUser } from '../../../queries/auth';

/*
* This screen shows the user's profile information, and allows the user to edit profile information.
* If the user starts updating their information, the "log out" button will become a "save edits" button.
* The birthday can only be set once per account. Once it is set, it cannot be changed again.
*/
function SettingsScreen() {
const { session, signOut } = useSession();
const [loading, setLoading] = useState(true);
Expand Down Expand Up @@ -109,6 +116,37 @@ function SettingsScreen() {
if (!session) resetAndPushToRouter('/auth/login');
}, [session]);

const showAlert = () =>
Alert.alert(
'Are you sure you want to delete your account?',
'',
[
{
text: 'OK',
onPress: onDelete,
style: 'destructive',
},
{
text: 'Cancel',
onPress: () => {},
style: 'cancel',
},
],
{
cancelable: true,
onDismiss: () => {},
},
);

const onDelete = async () => {
const uuid = session?.user.id;

if (uuid) {
deleteUser(uuid);
signOut();
}
};

const updateProfile = async () => {
try {
setLoading(true);
Expand Down Expand Up @@ -201,7 +239,39 @@ function SettingsScreen() {
</View>

<Text style={[globalStyles.h1, styles.heading]}>Settings</Text>
<Text style={[globalStyles.h2, styles.subheading]}>Account</Text>
<View
style={[
styles.subheading,
{ flex: 1, gap: 14, flexDirection: 'row' },
]}
>
<Text style={globalStyles.h2}>Account</Text>
<View
style={{
flex: 1,
flexDirection: 'row',
alignSelf: 'flex-end',
paddingBottom: 3,
}}
>
<Text style={[globalStyles.errorMessage, { color: colors.grey }]}>
(
</Text>
<TouchableOpacity onPress={showAlert}>
<Text
style={[
globalStyles.errorMessage,
{ color: colors.grey, textDecorationLine: 'underline' },
]}
>
Delete account
</Text>
</TouchableOpacity>
<Text style={[globalStyles.errorMessage, { color: colors.grey }]}>
)
</Text>
</View>
</View>

<View style={styles.staticData}>
<AccountDataDisplay label="First Name" value={firstName} />
Expand Down
12 changes: 9 additions & 3 deletions src/app/(tabs)/story/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ import globalStyles, { fonts } from '../../../styles/globalStyles';
import BackButton from '../../../components/BackButton/BackButton';
import OptionBar from '../../../components/OptionBar/OptionBar';

/*
* This screen displays the story based on the params `{ storyId: string }`
* It loads the story from supabase based on the storyId.
* It uses the `react-native-render-html` to render the stories HTML from wordpress.
* The user can also add the story to favorites/reading, and react to the story from this screen.
*/
function StoryScreen() {
const [isLoading, setLoading] = useState(true);
const scrollRef = React.useRef<any>(null);
Expand Down Expand Up @@ -84,9 +90,9 @@ function StoryScreen() {
horizontal
showsHorizontalScrollIndicator={false}
data={[
...story.genre_medium,
...story.tone,
...story.topic,
...(story.genre_medium ?? []),
...(story.tone ?? []),
...(story.topic ?? []),
].filter(tag => tag != null)}
keyExtractor={(_, index) => index.toString()} // Add a key extractor for performance optimization
renderItem={({ item, index }) => (
Expand Down
5 changes: 5 additions & 0 deletions src/app/auth/forgotPassword/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ import colors from '../../../styles/colors';
import globalStyles from '../../../styles/globalStyles';
import { useSession } from '../../../utils/AuthContext';

/*
* Handles the user's request to reset their password.
* Redirects to the verify page if the username/email is valid
* Uses `useDebounce` to check if the username/email is valid only when the user stops typing
*/
function ForgotPasswordScreen() {
const { resetPassword } = useSession();
const [email, setEmail] = useState('');
Expand Down
3 changes: 3 additions & 0 deletions src/app/auth/login/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import colors from '../../../styles/colors';
import globalStyles from '../../../styles/globalStyles';
import { useSession } from '../../../utils/AuthContext';

/*
* The screen to handle a user's login request
*/
function LoginScreen() {
const sessionHandler = useSession();
const [emailOrUsername, setEmailOrUsername] = useState('');
Expand Down
4 changes: 4 additions & 0 deletions src/app/auth/onboarding/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ import { useSession } from '../../../utils/AuthContext';
import supabase from '../../../utils/supabase';
import { SafeAreaView } from 'react-native-safe-area-context';

/*
* The screen that lets the user enter information about themselves that will be associated to their account
* The birthday can only be changed once, so a warning is shown if the user sets a birthday. If a birthday is not set, it can be set on the setting screen
*/
function OnboardingScreen() {
const { session, user } = useSession();
const [loading, setLoading] = useState(true);
Expand Down
4 changes: 4 additions & 0 deletions src/app/auth/resetPassword/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import { useSession } from '../../../utils/AuthContext';
import PasswordComplexityText from '../../../components/PasswordComplexityText/PasswordComplexityText';
import { isPasswordSameAsBefore } from '../../../queries/profiles';

/*
* The screen where the user enters their new password, after verifying their email.
* The user can only reset their password once all requirements are met.
*/
function ResetPasswordScreen() {
const { session, updateUser, signOut } = useSession();
const [password, setPassword] = useState('');
Expand Down
4 changes: 4 additions & 0 deletions src/app/auth/signup/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ import globalStyles from '../../../styles/globalStyles';
import { useSession } from '../../../utils/AuthContext';
import supabase from '../../../utils/supabase';

/*
* The screen where users can create a new account
* The user can only sign up once all the requirement are met
*/
function SignUpScreen() {
const { signUp } = useSession();

Expand Down
7 changes: 7 additions & 0 deletions src/app/auth/verify/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ import colors from '../../../styles/colors';
import globalStyles from '../../../styles/globalStyles';
import { useSession } from '../../../utils/AuthContext';

/*
* The OTP verification screen. This is where the user can enter the 6-digit code from their email.
* When redirecting to this screen, you need to pass in the parameters `{ finalRedirect: string, userEmail: string }`
* The final redirect is the path to redirect to if the verification is successful. The userEmail is for display purposes.
* The user can resend the code, and a `Toast` will appear if the resend was successful.
* If the code is invalid, a red error message under the OTP text input will be displayed
*/
function VerificationScreen() {
const { user, verifyOtp, resendVerification } = useSession();
const [errorMessage, setErrorMessage] = useState('');
Expand Down
6 changes: 6 additions & 0 deletions src/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@ import { useEffect, useState } from 'react';

import SplashScreen from '../components/SplashScreen/SplashScreen';
import { useSession } from '../utils/AuthContext';

import {
useFonts,
Manrope_700Bold,
Manrope_400Regular,
Manrope_600SemiBold,
} from '@expo-google-fonts/manrope';

/**
* The entry point to the app. While the app is loading, it shows the SplashScreen.
* It loads the fonts used in the app, and, if the user is signed it, it redirects the user to home.
* Otherwise, it redirects the user to the login page
*/
function StartPage() {
const { session, isLoading } = useSession();
const [delay, setDelay] = useState(true);
Expand Down
4 changes: 4 additions & 0 deletions src/components/AccountDataDisplay/AccountDataDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ type AccountDataDisplayProps = {
value: string | React.ReactNode;
};

/*
* Used on the setting page. Displays information about the user's account.
* For example label="First Name" value="John"
*/
function AccountDataDisplay({ label, value }: AccountDataDisplayProps) {
return (
<View style={styles.view}>
Expand Down
1 change: 1 addition & 0 deletions src/components/AuthorCard/AuthorCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type AuthorCardProps = {
bio: string;
artist_statement: string;
};

function AuthorCard({
name,
pronouns,
Expand Down
4 changes: 4 additions & 0 deletions src/components/AuthorImage/AuthorImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ type AuthorImageProps = {
// pressFunction: (event: GestureResponderEvent) => void;
};

/*
* Displays the author's profile picture in a circle with the text "Authors:" before it, followed by the author's name
* Used exclusively on the story screen
*/
function AuthorImage({
author_name,
author_id,
Expand Down
4 changes: 4 additions & 0 deletions src/components/BackButton/BackButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ type BackButtonProps = {
pressFunction: (event: GestureResponderEvent) => void;
};

/*
* Universal pre styled back button
* pressFunction will usually be `router.back()`
*/
function BackButton({ pressFunction }: BackButtonProps) {
return (
<TouchableOpacity onPress={pressFunction}>
Expand Down
5 changes: 5 additions & 0 deletions src/components/ContentCard/ContentCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ type ContentCardProps = {
pressFunction: (event: GestureResponderEvent) => void;
};

/*
* Displays a story for use in horizontal ScrollView
* Includes options to save the story, and see the reactions to a story
* Uses the PubSubContext to update its icons based on user interaction
*/
function ContentCard({
id,
title,
Expand Down
5 changes: 5 additions & 0 deletions src/components/FavoriteStoryButton/FavoriteStoryButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ type FavoriteStoryButtonProps = {
storyId: number;
};

/*
* A button that handles the state of the favorite story icon.
* Automatically updates using PubSubContext based on user interaction.
* Changes the icon displayed based on if the story is already favorited
*/
export default function FavoriteStoryButton({
storyId,
}: FavoriteStoryButtonProps) {
Expand Down
5 changes: 5 additions & 0 deletions src/components/FilterDropdown/FilterDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ type FilterDropdownProps = {
setter: React.Dispatch<React.SetStateAction<string[]>>;
};

/*
* Dropdown used for filtering stories
* Will change the border color to `selectedBorderColor` if an option is selected
* Primarily used on the search and genre screens
*/
function FilterDropdown({
placeholder,
value,
Expand Down
3 changes: 3 additions & 0 deletions src/components/GenreCard/GenreCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ type GenreCardProps = {
pressFunction: (event: GestureResponderEvent) => void;
};

/*
* Card displayed on the search screen, representing a single clickable genre tile.
*/
function GenreCard({ subgenres, pressFunction, cardColor }: GenreCardProps) {
return (
<TouchableOpacity onPress={pressFunction}>
Expand Down
Loading

0 comments on commit bc8af1f

Please sign in to comment.