From 5607655100fba959f5929a7c6a2a4f6c3bb4ba0d Mon Sep 17 00:00:00 2001 From: Taylor Finelli <85365584+taylorfinelli@users.noreply.github.com> Date: Mon, 8 Apr 2024 12:35:56 -0700 Subject: [PATCH 1/6] Friend model changes, New API, Friend Request Component changes (#185) * Need to take break from this and fix prisma schema * gotta merge. ah jeez * changes made to friends.ts, ready to test but cant seed * modified mock data * pass receiverId into friend request * honestly gang idk * Did this fix the global context? * works just working on animation * DID THIS FIX IT? * YES WE DID IT * add a useState hook that changes the component so it will delete immediately * modify test with new field * Fix this goofy test * make int on API --------- Co-authored-by: Taylor Finelli Co-authored-by: mva919 --- apps/expo/src/app/(tabs)/index.tsx | 19 ++- apps/expo/src/app/notif/misc/index.tsx | 74 +++++++----- .../__tests__/FriendRequestNotif.test.tsx | 69 ++++++++--- .../FriendRequestNotif.test.tsx.snap | 6 +- .../notif/miscNotifs/FriendRequestNotif.tsx | 54 ++++++++- .../notif/miscNotifs/miscNotifs.tsx | 14 ++- apps/expo/src/context/global-context.tsx | 54 +++++---- packages/api/src/router/friend.ts | 110 +++++++++++------- packages/db/prisma/schema.prisma | 49 ++++---- packages/db/src/mock-data/friend.json | 40 ------- 10 files changed, 294 insertions(+), 195 deletions(-) diff --git a/apps/expo/src/app/(tabs)/index.tsx b/apps/expo/src/app/(tabs)/index.tsx index 34f326ce..b9db3f1f 100644 --- a/apps/expo/src/app/(tabs)/index.tsx +++ b/apps/expo/src/app/(tabs)/index.tsx @@ -3,11 +3,14 @@ import { Text } from 'react-native' import { SafeAreaView } from 'react-native-safe-area-context' import { Stack } from 'expo-router' +import RotatingBarbellIcon from '~/components/notif/RotatingBarbellIcon' import { useGlobalContext } from '~/context/global-context' const Dashboard = () => { const { userData } = useGlobalContext() + console.log('userData', userData) + return ( {/* Define pour custom header */} @@ -17,11 +20,17 @@ const Dashboard = () => { }} /> */} Home screen - User Data - {userData?.id} - {userData?.clerkId} - {userData?.name} - {userData?.username} + {userData ? ( + <> + User Data + {userData?.id} + {userData?.clerkId} + {userData?.name} + {userData?.username} + + ) : ( + + )} ) } diff --git a/apps/expo/src/app/notif/misc/index.tsx b/apps/expo/src/app/notif/misc/index.tsx index 6b9868e7..e33fa74e 100644 --- a/apps/expo/src/app/notif/misc/index.tsx +++ b/apps/expo/src/app/notif/misc/index.tsx @@ -1,55 +1,71 @@ -import React, { useState } from 'react'; -import { View, Text, TouchableOpacity } from 'react-native'; -import { Ionicons, MaterialCommunityIcons } from '@expo/vector-icons'; -import { ScrollView } from 'react-native-gesture-handler'; +import React, { useState } from 'react' +import { Text, TouchableOpacity, View } from 'react-native' +import { ScrollView } from 'react-native-gesture-handler' +import { SafeAreaView } from 'react-native-safe-area-context' import { router } from 'expo-router' -import MiscNotifs from '~/components/notif/miscNotifs/miscNotifs'; -import DmNotifs from '~/components/notif/dmNotifs/dmNotifs'; -import GcNotifs from '~/components/notif/gcNotifs/gcNotifs'; -import { SafeAreaView } from 'react-native-safe-area-context'; -export type notifsType = "misc" | "dm" | "gc" +import { Ionicons, MaterialCommunityIcons } from '@expo/vector-icons' + +import DmNotifs from '~/components/notif/dmNotifs/dmNotifs' +import GcNotifs from '~/components/notif/gcNotifs/gcNotifs' +import MiscNotifs from '~/components/notif/miscNotifs/miscNotifs' + +export type notifsType = 'misc' | 'dm' | 'gc' export default function NotifScreen() { - - const [visibleNotifs, setVisibleNotifs] = useState("misc"); - + const [visibleNotifs, setVisibleNotifs] = useState('misc') + return ( - + {/*header w/ back and new message buttons*/} router.back()} name="chevron-back" size={24} color="#CACACA" /> - Notifications + Notifications {/*notif nav buttons*/} - - {setVisibleNotifs("misc")}}> - Notifications + + { + setVisibleNotifs('misc') + }} + > + General - {setVisibleNotifs("dm")}}> - Direct + { + setVisibleNotifs('dm') + }} + > + Direct - {setVisibleNotifs("gc")}}> - Group + { + setVisibleNotifs('gc') + }} + > + Group {/*thin line between notifs and nav section*/} - + {/*list of notifications*/} - {visibleNotifs == "misc" && } - {visibleNotifs == "dm" && } - {visibleNotifs == "gc" && } + {visibleNotifs == 'misc' && } + {visibleNotifs == 'dm' && } + {visibleNotifs == 'gc' && } - ); + ) } diff --git a/apps/expo/src/components/notif/__tests__/FriendRequestNotif.test.tsx b/apps/expo/src/components/notif/__tests__/FriendRequestNotif.test.tsx index 1a49c627..fa102f71 100644 --- a/apps/expo/src/components/notif/__tests__/FriendRequestNotif.test.tsx +++ b/apps/expo/src/components/notif/__tests__/FriendRequestNotif.test.tsx @@ -1,24 +1,55 @@ import React from 'react' -import { fireEvent, render, screen } from '@testing-library/react-native' -import FriendRequestNotif from '~/components/notif/miscNotifs/FriendRequestNotif' + import { Notification } from '@prisma/client' +import { render, screen } from '@testing-library/react-native' + +import FriendRequestNotif from '~/components/notif/miscNotifs/FriendRequestNotif' + +jest.mock('~/utils/api', () => ({ + __esModule: true, + api: { + useUtils: jest.fn(() => ({ + notif: { + getMiscNotifsWithSenderUsernameFromUserId: { + invalidate: jest.fn(), + }, + }, + })), + friend: { + makeFriendsReceiverIdSenderId: { + useMutation: jest.fn(() => ({ + mutateAsync: jest.fn(), + })), + }, + }, + }, +})) + +describe('FriendRequestNotif', () => { + it("should render sender's username and content", async () => { + const expectedNotif: Notification = { + id: 3, + createdAt: new Date('2024-03-14T10:15:00Z'), + content: 'wants to be your friend', + type: 'FRIEND_REQUEST', + read: false, + receiverId: 4, + senderId: 2, + } + const expectedSenderUsername = 'username' + const expectedContent = expectedNotif.content + const expectedReceiverId = expectedNotif.receiverId -test('FriendRequestNotif', async () => { - const expectedNotif: Notification = { - "id": 3, - "createdAt": new Date("2024-03-14T10:15:00Z"), - "content": "wants to be your friend", - "type": "FRIEND_REQUEST", - "read": false, - "receiverId": 4, - "senderId": 2 - } - const expectedSenderUsername = 'username' - const expectedContent = expectedNotif.content + render( + , + ) + const senderUsernameOutput = await screen.findByTestId('sender-username-with-content') - render() - const senderUsernameOutput = await screen.findByTestId('sender-username-with-content') - - expect(senderUsernameOutput).toHaveTextContent(expectedSenderUsername + ' ' + expectedContent) - expect(screen.toJSON()).toMatchSnapshot() + expect(senderUsernameOutput).toHaveTextContent(expectedSenderUsername + ' ' + expectedContent) + expect(screen.toJSON()).toMatchSnapshot() + }) }) diff --git a/apps/expo/src/components/notif/__tests__/__snapshots__/FriendRequestNotif.test.tsx.snap b/apps/expo/src/components/notif/__tests__/__snapshots__/FriendRequestNotif.test.tsx.snap index f17cd893..16bf51d5 100644 --- a/apps/expo/src/components/notif/__tests__/__snapshots__/FriendRequestNotif.test.tsx.snap +++ b/apps/expo/src/components/notif/__tests__/__snapshots__/FriendRequestNotif.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`FriendRequestNotif 1`] = ` +exports[`FriendRequestNotif should render sender's username and content 1`] = ` { + setComponent(<>) + friendsMutation.mutateAsync({ + receiverId: receiverId, + senderId: notif.senderId!, + accepted: true, + notificationId: notif.id, + }) + } + + const handleDeclineFriend = () => { + setComponent(<>) + friendsMutation.mutateAsync({ + receiverId: receiverId, + senderId: notif.senderId!, + accepted: false, + notificationId: notif.id, + }) + } + + // unhinged, but it works LOL + // we do this so it will instantly remove the notification from the list + // TRPC invalidator takes too long to remove it + const [component, setComponent] = useState( {/*photo, use icon for now, probably use profile photo in the future*/} @@ -25,12 +59,20 @@ export default function FriendRequestNotif({ notif, senderUsername }: FriendRequ {/*accept and decline buttons*/} - + Accept - + Decline @@ -41,6 +83,8 @@ export default function FriendRequestNotif({ notif, senderUsername }: FriendRequ {/*thin line between notifications*/} - + , ) + + return component } diff --git a/apps/expo/src/components/notif/miscNotifs/miscNotifs.tsx b/apps/expo/src/components/notif/miscNotifs/miscNotifs.tsx index c577e29b..b757f958 100644 --- a/apps/expo/src/components/notif/miscNotifs/miscNotifs.tsx +++ b/apps/expo/src/components/notif/miscNotifs/miscNotifs.tsx @@ -1,3 +1,4 @@ +import { useEffect } from 'react' import { Text, View } from 'react-native' import RotatingBarbellIcon from '~/components/notif/RotatingBarbellIcon' @@ -6,9 +7,16 @@ import { api } from '~/utils/api' import FriendRequestNotif from './FriendRequestNotif' import NudgeNotif from './NudgeNotif' -const handleNotif = (notif: any, id: number) => { +const handleNotif = (notif: any, id: number, receiverId: number) => { if (notif.type == 'FRIEND_REQUEST') { - return + return ( + + ) } else if (notif.type == 'NUDGE') { return } @@ -30,7 +38,7 @@ export default function MiscNotifs() { for (let i = 0; i < data.length; i++) { const notif = data[i] if (notif) { - renderedNotifications?.push(handleNotif(notif, notif.id)) + renderedNotifications?.push(handleNotif(notif, notif.id, userData.id)) } } } diff --git a/apps/expo/src/context/global-context.tsx b/apps/expo/src/context/global-context.tsx index da7975bf..b39ac965 100644 --- a/apps/expo/src/context/global-context.tsx +++ b/apps/expo/src/context/global-context.tsx @@ -1,6 +1,11 @@ import type { Dispatch, SetStateAction } from 'react' -import { createContext, useCallback, useContext, useEffect, useState } from 'react' - +import { + createContext, + useCallback, + useContext, + useEffect, + useState, +} from 'react' import { useClerk } from '@clerk/clerk-expo' import { api } from '~/utils/api' @@ -33,28 +38,16 @@ const GlobalContextProvider = ({ children }: IGlobalContextProviderProps) => { const [userData, setUserData] = useState(null) const { user: clerkUserData } = useClerk() const createUser = api.user.create.useMutation() + const { data: userNineData, isFetched: userNineDataIsFetched } = + api.user.byId.useQuery({ id: 9 }) const getUserData = useCallback(async () => { if (clerkUserData) { - console.log('DEVELOPMENT EVIRONMENT') - - if (process.env.NODE_ENV === 'development') { - const userNine = api.user.byId.useQuery({ id: 9 }) - - if (userNine.data) { - setUserData({ - id: userNine.data.id, - clerkId: userNine.data.clerkId, - username: userNine.data.username, - name: userNine.data.name!, - }) - return - } - } - const response = await createUser.mutateAsync({ clerkId: clerkUserData.id, - username: clerkUserData.username ? clerkUserData.username : generateUsername(), + username: clerkUserData.username + ? clerkUserData.username + : generateUsername(), name: clerkUserData.fullName ? clerkUserData.fullName : 'User', }) @@ -68,8 +61,17 @@ const GlobalContextProvider = ({ children }: IGlobalContextProviderProps) => { }, [clerkUserData]) useEffect(() => { - getUserData() - }, [getUserData]) + if (process.env.NODE_ENV === 'development') { + if (!userNineDataIsFetched) return + + setUserData({ + id: userNineData?.id!, + clerkId: userNineData?.clerkId!, + username: userNineData?.username!, + name: userNineData?.name!, + }) + } else getUserData() + }, [getUserData, userNineDataIsFetched]) const globalContextValue: TGlobalContext = { isWorkingOut, @@ -77,13 +79,19 @@ const GlobalContextProvider = ({ children }: IGlobalContextProviderProps) => { userData, } - return {children} + return ( + + {children} + + ) } export const useGlobalContext = () => { const context = useContext(GlobalContext) if (!context) { - throw new Error('useGlobalContext must be used within a GlobalContextProvider') + throw new Error( + 'useGlobalContext must be used within a GlobalContextProvider', + ) } return context } diff --git a/packages/api/src/router/friend.ts b/packages/api/src/router/friend.ts index 24b9ed72..1187dad3 100644 --- a/packages/api/src/router/friend.ts +++ b/packages/api/src/router/friend.ts @@ -1,5 +1,6 @@ -import { z } from 'zod'; -import { createTRPCRouter, publicProcedure } from '../trpc'; +import { z } from 'zod' + +import { createTRPCRouter, publicProcedure } from '../trpc' export const friendRouter = createTRPCRouter({ /** @@ -9,67 +10,86 @@ export const friendRouter = createTRPCRouter({ return ctx.prisma.friend.findMany({ orderBy: { id: 'asc' }, include: { user: true }, - }); + }) }), /** * Get friend by id */ - byId: publicProcedure - .input(z.object({ id: z.number() })) - .query(({ ctx, input }) => { - return ctx.prisma.friend.findFirst({ - where: { id: input.id }, - include: { user: true }, - }); - }), + byId: publicProcedure.input(z.object({ id: z.number() })).query(({ ctx, input }) => { + return ctx.prisma.friend.findFirst({ + where: { id: input.id }, + include: { user: true }, + }) + }), /** - * Create a friend + * @remarks + * This accepts or denies a friend request for the user. + * + * @param receiverId - the id of the one who receives, logged in user + * @param senderId - the id of the one who sends, *not* logged in user + * @returns the accept or deny mutation, adds as friend or does not add */ - create: publicProcedure + makeFriendsReceiverIdSenderId: publicProcedure .input( z.object({ - userId: z.number(), - }) + receiverId: z.number().int(), + senderId: z.number().int(), + accepted: z.boolean(), + notificationId: z.number().int(), + }), ) - .mutation(({ ctx, input }) => { - return ctx.prisma.friend.create({ - data: { - user: { connect: { id: input.userId } }, + .mutation(async ({ ctx, input }) => { + if (input.accepted) { + await ctx.prisma.friend.create({ + data: { + friendId: input.receiverId, + userId: input.senderId, + }, + }) + + await ctx.prisma.friend.create({ + data: { + friendId: input.senderId, + userId: input.receiverId, + }, + }) + } + + await ctx.prisma.notification.delete({ + where: { + id: input.notificationId, }, - include: { user: true }, - }); + }) }), /** * Delete a friend */ - delete: publicProcedure - .input(z.object({ id: z.number() })) - .mutation(({ ctx, input }) => { - return ctx.prisma.friend.delete({ - where: { id: input.id }, - }); - }), + delete: publicProcedure.input(z.object({ id: z.number() })).mutation(({ ctx, input }) => { + return ctx.prisma.friend.delete({ + where: { id: input.id }, + }) + }), /** * Update a friend: UNUSED, but here for reference */ -// update: publicProcedure -// .input( -// z.object({ -// id: z.number(), -// userId: z.number().optional(), -// }) -// ) -// .mutation(({ ctx, input }) => { -// return ctx.prisma.friend.update({ -// where: { id: input.id }, -// data: { -// user: input.userId ? { connect: { id: input.userId } } : undefined, -// }, -// include: { user: true }, -// }); -// }), -}); \ No newline at end of file + // update: publicProcedure + // .input( + // z.object({ + // id: z.number(), + // userId: z.number().optional(), + // }) + // ) + // .mutation(({ ctx, input }) => { + // return ctx.prisma.friend.update({ + // where: { id: input.id }, + // data: { + // user: input.userId ? { connect: { id: input.userId } } : undefined, + // }, + // include: { user: true }, + // }); + // }), +}) diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma index 52509117..54587ab7 100644 --- a/packages/db/prisma/schema.prisma +++ b/packages/db/prisma/schema.prisma @@ -105,18 +105,21 @@ model User { notificationsBanners Boolean @default(true) } -// ****************************************************************************************** -// * Friends Collection -// * id: Auto-incremented unique identifier. -// * user: The User model representing one side of the friendship. -// * userId: The identifier for the user, establishing the relation to the User model. -// ****************************************************************************************** -model Friend { - id Int @id @unique @default(autoincrement()) + // ****************************************************************************************** + // * Friends Collection + // * id: Auto-incremented unique identifier. + // * user: The User model representing one side of the friendship. + // * userId: The identifier for the user, establishing the relation to the User model. + // * friendId: The identifier for the friend, used to query friend info. + // ****************************************************************************************** + model Friend { + id Int @id @unique @default(autoincrement()) - user User @relation(fields: [userId], references: [id]) - userId Int -} + user User @relation(fields: [userId], references: [id]) + userId Int + + friendId Int + } // ****************************************************************************************** // * Log model @@ -248,18 +251,18 @@ model Notification { senderId Int? } -model Chat { - id Int @id @unique @default(autoincrement()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - name String? @db.Char(40) - users User[] - readByUserIds Int[] - createdByUserId Int - messages Message[] - type ChatType - createdBy User @relation("ChatCreator", fields: [createdByUserId], references: [id]) -} + model Chat { + id Int @id @unique @default(autoincrement()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + name String? @db.Char(40) + users User[] + readByUserIds Int[] + messages Message[] + type ChatType + createdByUserId Int + createdBy User @relation("ChatCreator", fields: [createdByUserId], references: [id]) + } model Message { id Int @id @unique @default(autoincrement()) diff --git a/packages/db/src/mock-data/friend.json b/packages/db/src/mock-data/friend.json index c8457cdd..32960f8c 100644 --- a/packages/db/src/mock-data/friend.json +++ b/packages/db/src/mock-data/friend.json @@ -1,42 +1,2 @@ [ - { - "id": 1, - "userId": 10 - }, - { - "id": 2, - "userId": 5 - }, - { - "id": 3, - "userId": 1 - }, - { - "id": 4, - "userId": 2 - }, - { - "id": 5, - "userId": 3 - }, - { - "id": 6, - "userId": 4 - }, - { - "id": 7, - "userId": 6 - }, - { - "id": 8, - "userId": 3 - }, - { - "id": 9, - "userId": 5 - }, - { - "id": 10, - "userId": 5 - } ] \ No newline at end of file From 7def7e0f8cc327e2334afab254c8af272261e3b6 Mon Sep 17 00:00:00 2001 From: Taylor Finelli <85365584+taylorfinelli@users.noreply.github.com> Date: Mon, 8 Apr 2024 18:41:47 -0700 Subject: [PATCH 2/6] Frontend -- Search bar for filtering a list of items (#187) * search bar for filtering a list of items * add documentation to search bar component * remove superfluous comment * change type of setFilteredList in search bar props --------- Co-authored-by: Taylor Finelli --- .../ui/search-bar/SearchBar.test.tsx | 35 ++++++++++++ .../components/ui/search-bar/SearchBar.tsx | 55 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 apps/expo/src/components/ui/search-bar/SearchBar.test.tsx create mode 100644 apps/expo/src/components/ui/search-bar/SearchBar.tsx diff --git a/apps/expo/src/components/ui/search-bar/SearchBar.test.tsx b/apps/expo/src/components/ui/search-bar/SearchBar.test.tsx new file mode 100644 index 00000000..4b79a157 --- /dev/null +++ b/apps/expo/src/components/ui/search-bar/SearchBar.test.tsx @@ -0,0 +1,35 @@ +import React, { useState } from 'react' + +import { fireEvent, render, screen } from '@testing-library/react-native' + +import SearchBar from './SearchBar' + +test('SearchBar', async () => { + const TestComponent = () => { + const item1 = { + id: 1, + name: 'ab', + } + const item2 = { + id: 2, + name: 'abc', + } + const item3 = { + id: 3, + name: 'bac', + } + + const listToFilter = [item1, item2, item3] + const [filteredList, setFilteredList] = useState(listToFilter) + + return + } + + render() + + // Find the search input + const searchInput = screen.getByPlaceholderText('Search...') + + // Simulate typing in the search input + fireEvent.changeText(searchInput, 'a') // Type 'a' +}) diff --git a/apps/expo/src/components/ui/search-bar/SearchBar.tsx b/apps/expo/src/components/ui/search-bar/SearchBar.tsx new file mode 100644 index 00000000..1199939b --- /dev/null +++ b/apps/expo/src/components/ui/search-bar/SearchBar.tsx @@ -0,0 +1,55 @@ +/** + * HOW TO USE: + * -------------------------------------------------------------------------------- + * Inside of your PARENT component, you need to have the following: + * - list: any[] + * - This is your raw data that you get from a query + * - const[filteredList, setFilteredList] = useState(List) + * - filteredList is what is going to be displayed + * - setFilteredList is how we set the values that are going to be outputted + * - filterBy: string + * - this is the field that you want to filter your data by + * - placeholder: string + * - placeholder text within the search bar + * -------------------------------------------------------------------------------- + * To output the data, you want to use a map and map the items within filteredList + * to your list item component. + * + * For example: + * + * {filteredList.map((item, index) => ( + * + * {item.name} + * + * ))} + */ + +import React, { useEffect, useState } from 'react' +import { View } from 'react-native' +import { TextInput } from 'react-native-gesture-handler' + +interface SearchBarProps { + list: any[] + setFilteredList: React.Dispatch> + filterBy: string + placeholder: string +} + +export default function SearchBar({ list, setFilteredList, filterBy, placeholder }: SearchBarProps) { + const [searchTerm, setSearchTerm] = useState('') + + useEffect(() => { + setFilteredList(list.filter((item) => item[filterBy].toLowerCase().includes(searchTerm.toLowerCase()))) + }, [searchTerm]) + + return ( + + + + ) +} From d7660877a0ce2984b5d5fc644c1b37294cee1d1c Mon Sep 17 00:00:00 2001 From: bigboidanwithacan <38168108+bigboidanwithacan@users.noreply.github.com> Date: Thu, 11 Apr 2024 17:32:04 -0700 Subject: [PATCH 3/6] 152 backed api endpoints for workout collection and bugs (#184) * Fixed issues - pre test * Fix workout, exercise, set, and user model relations * changes for testing * fix set schema * temporary push * Update index.tsx * EVERYTHING WORKS * remove changes for testing * Fix issues noticed by Taylor and Marcos Deleted commented out code Most importantly added back the default field attribute for streaks --- packages/api/src/router/workout.ts | 24 +++--------- packages/db/prisma/schema.prisma | 26 +++++++------ packages/db/src/mock-data/exercise.json | 39 +++++++------------ .../mock-data/{log.json => workoutLog.json} | 0 packages/db/src/scripts/seed.ts | 13 +++---- 5 files changed, 37 insertions(+), 65 deletions(-) rename packages/db/src/mock-data/{log.json => workoutLog.json} (100%) diff --git a/packages/api/src/router/workout.ts b/packages/api/src/router/workout.ts index 0a59aadf..254ff1a6 100644 --- a/packages/api/src/router/workout.ts +++ b/packages/api/src/router/workout.ts @@ -30,28 +30,11 @@ export const workoutRouter = createTRPCRouter({ finishedAt: z.date(), exercises: z.array( z.object({ - name: z.string(), - note: z.string().optional(), - // ExerciseCreateWithoutWorkoutInput calls SetCreateNestedManyWithoutExerciseInput which means that the sets field isn't needed? - // this should be the case because the exercises are already linked to the appropriate sets - // sets: z.array(z.object({ - // type: z.enum(['NORMAL', 'WARMUP', 'DROPSET', 'FAILURE']), - // reps: z.number().int().nonnegative(), - // weight: z.number().nonnegative(), - // exerciseID: z.number().int() - // })), - body_part: z.nativeEnum(BodyPart), - category: z.nativeEnum(Category), + id: z.number().int(), }), ), // Past workouts should not be needed since there is no history of it being used if it was newly created - // past_workouts: z.array(z.object({ - // createdAt: z.date(), - // updatedAt: z.date(), - // finishedAt: z.date(), - // userId: z.number().int(), - // })), userId: z.number().int(), }), ) @@ -65,7 +48,10 @@ export const workoutRouter = createTRPCRouter({ duration: input.duration, finishedAt: input.finishedAt, exercises: { - create: input.exercises, + connect: + input.exercises?.map((exercise)=>({ + id: exercise.id, + })), }, userId: input.userId, }, diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma index 54587ab7..304a78f4 100644 --- a/packages/db/prisma/schema.prisma +++ b/packages/db/prisma/schema.prisma @@ -94,7 +94,7 @@ model User { streak Int @default(0) posts Post[] friends Friend[] - log Log[] + workoutHistory WorkoutLog[] workouts Workout[] awards Award[] notifications Notification[] @relation("NotificationReceiver") @@ -103,6 +103,7 @@ model User { chats Chat[] spotify SpotifyData? notificationsBanners Boolean @default(true) + customExercises Exercise[] } // ****************************************************************************************** @@ -132,7 +133,7 @@ model User { // * workout: The workout that was completed. // * workoutId: The identifier for the workout. // ****************************************************************************************** -model Log { +model WorkoutLog { id Int @id @unique @default(autoincrement()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@ -153,16 +154,16 @@ model Log { // * Award: Represents achievements or milestones reached by users, with a name, description, and the user who received it. // ****************************************************************************************** model Workout { - id Int @id @unique @default(autoincrement()) - name String @db.Char(100) + id Int @id @unique @default(autoincrement()) + name String @db.Char(100) description String? duration Int - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt finishedAt DateTime - likes Int @default(0) + likes Int @default(0) exercises Exercise[] - past_workouts Log[] + past_workouts WorkoutLog[] user User @relation(fields: [userId], references: [id]) userId Int @@ -175,14 +176,15 @@ model Workout { // * GROUP: Group message // ****************************************************************************************** model Exercise { - id Int @id @unique @default(autoincrement()) - name String @db.Char(100) + id Int @id @unique @default(autoincrement()) + name String @db.Char(100) note String? sets Set[] body_part BodyPart category Category - workout Workout @relation(fields: [workoutId], references: [id]) - workoutId Int + workout Workout[] + User User? @relation(fields: [userId], references: [id]) + userId Int? } // ****************************************************************************************** diff --git a/packages/db/src/mock-data/exercise.json b/packages/db/src/mock-data/exercise.json index c9493928..f2a130dc 100644 --- a/packages/db/src/mock-data/exercise.json +++ b/packages/db/src/mock-data/exercise.json @@ -4,103 +4,90 @@ "name": "Arnold Dumbbell Press", "note": " targets shoulders and triceps.", "body_part": "SHOULDERS", - "category": "DUMBBELL", - "workoutId": 1 + "category": "DUMBBELL" }, { "id": 2, "name": "Seated Barbell Twist", "note": " targets the abdominal muscles.", "body_part": "CORE", - "category": "BARBELL", - "workoutId": 3 + "category": "BARBELL" }, { "id": 3, "name": "Calf-Machine Shoulder Shrug", "note": "targets the traps.", "body_part": "BACK", - "category": "MACHINE", - "workoutId": 2 + "category": "MACHINE" }, { "id": 4, "name": "Bodyweight Squat", "note": "targets the quadriceps, glutes, and hamstrings.", "body_part": "LEGS", - "category": "BODYWEIGHT", - "workoutId": 1 + "category": "BODYWEIGHT" }, { "id": 5, "name": "Band Assisted Pull-up", "note": "targets the lats, abdominals, forearms, and the middle back.", "body_part": "BACK", - "category": "ASSISTED_BODYWEIGHT", - "workoutId": 2 + "category": "ASSISTED_BODYWEIGHT" }, { "id": 6, "name": "Weighted Sissy Squat", "note": "targets the quadriceps, calves, glutes, and the hamstrings.", "body_part": "LEGS", - "category": "WEIGHTED_BODYWEIGHT", - "workoutId": 3 + "category": "WEIGHTED_BODYWEIGHT" }, { "id": 7, "name": "Treadmill Run", "note": null, "body_part": "OTHER", - "category": "CARDIO", - "workoutId": 1 + "category": "CARDIO" }, { "id": 8, "name": "Plank", "note": "trains the abdominals.", "body_part": "CORE", - "category": "DURATION", - "workoutId": 1 + "category": "DURATION" }, { "id": 9, "name": "Pullups", "note": "targets the lats, biceps, and the middle back.", "body_part": "BACK", - "category": "REPS_ONLY", - "workoutId": 2 + "category": "REPS_ONLY" }, { "id": 10, "name": "World's Greatest Stretch", "note": "This is a stretch designed to target the hamstrings, calves, glutes, and quadriceps", "body_part": "LEGS", - "category": "OTHER", - "workoutId": 3 + "category": "OTHER" }, { "id": 11, "name": "Dumbbell Bicep Curl", "note": "targets the biceps and forearms.", "body_part": "ARMS", - "category": "DUMBBELL", - "workoutId": 2 + "category": "DUMBBELL" }, { "id": 12, "name": "Incline Bench Press", "note": "targets the chest, shoulders, and triceps.", "body_part": "CHEST", - "category": "BARBELL", - "workoutId": 2 + "category": "BARBELL" }, { "id": 13, "name": "Farmer's Walk", "note": " targets multiple body parts, including the forearms, traps, the core, and legs.", "body_part": "FULL_BODY", - "category": "WEIGHTED_BODYWEIGHT", - "workoutId": 1 + "category": "WEIGHTED_BODYWEIGHT" } ] diff --git a/packages/db/src/mock-data/log.json b/packages/db/src/mock-data/workoutLog.json similarity index 100% rename from packages/db/src/mock-data/log.json rename to packages/db/src/mock-data/workoutLog.json diff --git a/packages/db/src/scripts/seed.ts b/packages/db/src/scripts/seed.ts index 3765722b..0d9ba93c 100644 --- a/packages/db/src/scripts/seed.ts +++ b/packages/db/src/scripts/seed.ts @@ -5,7 +5,7 @@ import award from '../mock-data/award.json' import chat from '../mock-data/chat.json' import exercise from '../mock-data/exercise.json' import friend from '../mock-data/friend.json' -import log from '../mock-data/log.json' +import workoutLog from '../mock-data/workoutLog.json' import message from '../mock-data/message.json' import notification from '../mock-data/notification.json' import post from '../mock-data/post.json' @@ -58,15 +58,12 @@ const loaddb = async () => { await prisma.exercise.deleteMany() logger('delete', 'exercise') - await prisma.log.deleteMany() - logger('delete', 'log') + await prisma.workoutLog.deleteMany() + logger('delete', 'workoutLog') await prisma.workout.deleteMany() logger('delete', 'workout') - await prisma.log.deleteMany() - logger('delete', 'log') - await prisma.friend.deleteMany() logger('delete', 'friend') @@ -158,8 +155,8 @@ const loaddb = async () => { logger('add', 'friend') /* */ - await prisma.log.createMany({ - data: log as Prisma.LogCreateManyInput[], + await prisma.workoutLog.createMany({ + data: workoutLog as Prisma.WorkoutLogCreateManyInput[], }) logger('add', 'log') From c6bf4b66bed67e8aadc18eb02e6f99b8fb35a3d4 Mon Sep 17 00:00:00 2001 From: AaronFmyHub <146596967+AaronFmyHub@users.noreply.github.com> Date: Thu, 11 Apr 2024 19:32:49 -0700 Subject: [PATCH 4/6] 42-frontend-new workout screen structure (#188) * Add core comp * Use Body in index.tsx * Exclude body parts * Add test * View overlay issue FIX --- apps/expo/package.json | 3 +- apps/expo/src/app/award/index.tsx | 5 - apps/expo/src/app/muscleGroup/index.tsx | 226 ++++++++++++++++++ apps/expo/src/app/nav/index.tsx | 1 + .../muscleGroup/FrontBackSwitch.test.tsx | 8 + .../muscleGroup/FrontBackSwitch.tsx | 123 ++++++++++ apps/expo/src/components/toggle/Toggle.tsx | 42 ++-- pnpm-lock.yaml | 43 +++- 8 files changed, 413 insertions(+), 38 deletions(-) create mode 100644 apps/expo/src/app/muscleGroup/index.tsx create mode 100644 apps/expo/src/components/muscleGroup/FrontBackSwitch.test.tsx create mode 100644 apps/expo/src/components/muscleGroup/FrontBackSwitch.tsx diff --git a/apps/expo/package.json b/apps/expo/package.json index ac9ca141..00909448 100644 --- a/apps/expo/package.json +++ b/apps/expo/package.json @@ -45,13 +45,14 @@ "react": "18.2.0", "react-dom": "18.2.0", "react-native": "0.72.10", + "react-native-body-highlighter": "3.0.0-beta.6", "react-native-dotenv": "^3.4.10", "react-native-gesture-handler": "~2.12.1", "react-native-get-random-values": "^1.9.0", "react-native-reanimated": "~3.3.0", "react-native-safe-area-context": "4.6.3", "react-native-screens": "~3.22.1", - "react-native-svg": "^13.9.0", + "react-native-svg": "^14.1.0", "react-native-svg-transformer": "^1.3.0", "rn-sliding-up-panel": "^2.4.6", "superjson": "2.2.1", diff --git a/apps/expo/src/app/award/index.tsx b/apps/expo/src/app/award/index.tsx index f03a6134..fa52481b 100644 --- a/apps/expo/src/app/award/index.tsx +++ b/apps/expo/src/app/award/index.tsx @@ -38,11 +38,6 @@ export default function Award() { /> - - - - - ) } diff --git a/apps/expo/src/app/muscleGroup/index.tsx b/apps/expo/src/app/muscleGroup/index.tsx new file mode 100644 index 00000000..edabc30a --- /dev/null +++ b/apps/expo/src/app/muscleGroup/index.tsx @@ -0,0 +1,226 @@ +import React, { useState } from 'react' +import { Dimensions, Image, Text, View, StyleSheet } from 'react-native' +import { router } from 'expo-router' +import tailwind from '@/tooling/tailwind' +import { Ionicons, MaterialCommunityIcons } from '@expo/vector-icons' +import { SafeAreaView } from 'react-native-safe-area-context' +import Body from 'react-native-body-highlighter' +import ToggleSwitch from '~/components/toggle/Toggle' +import FrontBackSwitch from '~/components/muscleGroup/FrontBackSwitch' + +export type muscleSelectDev = + | "abs" + | "adductors" + | "ankles" + | "biceps" + | "calves" + | "chest" + | "deltoids" + | "feet" + | "forearm" + | "gluteal" + | "hamstring" + | "hands" + | "hair" + | "head" + | "knees" + | "lower-back" + | "neck" + | "obliques" + | "quadriceps" + | "tibialis" + | "trapezius" + | "triceps" + | "upper-back"; + +export type muscleSelectUser = + | "Shoulder" + | "Chest" + | "Arm" + | "Core" + | "Upper leg" + | "Lower leg" + | "Upper back" + | "Lat" + | "Lower back" + | "Glute"; + +export default function NewWorkoutMuscleGroup() { + const { width: screenWidth, height: screenHeight } = Dimensions.get('window') + const vmin70 = Math.min(screenWidth, screenHeight) * 0.7 + + const [bodyPartSelected, setBodyPartSelected] = useState({/* + slug: "biceps", + intensity: 2,*/ + } + ) + const [isBackSideEnabled, setIsBackSideEnabled] = useState(false) + const [isMale, setIsMale] = useState(true) + const toggleSwitch = () => + setIsBackSideEnabled((previousState) => !previousState) + + const toggleGenderSwitch = () => setIsMale((previousState) => !previousState) + + //let tmp = ""; + let sameGroup = {}; + const [tmp1, setTmp1] = useState({}) + const [userSelection, setUserSelection] = useState(""); + + return ( + + + router.back()} + name="chevron-back" + size={24} + color="#CACACA" + /> + Select a muscle group + + + + + + + { + switch (e.slug) { + case "deltoids": + // Shoulder + setBodyPartSelected({ slug: e.slug, intensity: 2 }) + setUserSelection("Shoulder"); + + // setTmp1({ slug: "chest", intensity: 2 }) + // sameGroup = bodyPartSelected; + // sameGroup.slug = "chest"; + break; + + case "chest": + // Chest + setBodyPartSelected({ slug: e.slug, intensity: 2 }) + setUserSelection("Chest") + break; + + case "biceps": + case "triceps": + case "forearm": + // Arm + setBodyPartSelected({ slug: e.slug, intensity: 2 }) + setUserSelection("Arm") + break; + + case "abs": + case "obliques": + // Core + setBodyPartSelected({ slug: e.slug, intensity: 2 }) + setUserSelection("Core") + break; + + case "quadriceps": + case "adductors": + case "hamstring": + // Upper leg + setBodyPartSelected({ slug: e.slug, intensity: 2 }) + setUserSelection("Upper leg") + break; + + case "tibialis": + case "calves": + // Lower leg + setBodyPartSelected({ slug: e.slug, intensity: 2 }) + setUserSelection("Lower leg") + break; + + case "trapezius": + // Conflit with upper back + // Go with the figma design + // Upper back + setBodyPartSelected({ slug: e.slug, intensity: 2 }) + setUserSelection("Upper back") + break; + + case "upper-back": + // Lat + setBodyPartSelected({ slug: e.slug, intensity: 2 }) + setUserSelection("Lat") + break; + + case "lower-back": + // Lower back + setBodyPartSelected({ slug: e.slug, intensity: 2 }) + setUserSelection("Lower back") + break; + + case "gluteal": + // Glute + setBodyPartSelected({ slug: e.slug, intensity: 2 }) + setUserSelection("Glute") + break; + + default: + // Do Nothing + // Not supported + setUserSelection("") + break; + } + + // Get out + // setBodyPartSelected({ slug: e.slug, intensity: 2 }) + } + } + + + data={ + [ + /* + { slug: "chest", intensity: 1 }, + { slug: "abs", intensity: 2 }, + { slug: "upper-back", intensity: 1 }, + { slug: "lower-back", intensity: 2 },*/ + // tmp1, + bodyPartSelected + ]} + + gender={isMale ? "male" : "female"} + side={isBackSideEnabled ? "back" : "front"} + scale={1.7} + /> + + + + {/* Bottom section */} + + + + + + + Muscle Group Selected: {userSelection} + + + + {/* + + {isMale ? "Male" : "Female"} + + + + */} + + + + + + + + ) +} + diff --git a/apps/expo/src/app/nav/index.tsx b/apps/expo/src/app/nav/index.tsx index 2aadc72a..7044ef02 100644 --- a/apps/expo/src/app/nav/index.tsx +++ b/apps/expo/src/app/nav/index.tsx @@ -34,6 +34,7 @@ const Nav = () => { { key: 'Award', route: '/award' }, { key: 'Spotify', route: '/spotify' }, { key: 'Tracker', route: '/tracker' }, + { key: 'MuscleGroup', route: '/muscleGroup' }, ]} renderItem={({ item }) => ( + + + */} + + { + console.log({ selectedItem, index }) + }} + cancelButtonTextStyle={{ fontSize: 18 }} + onCancelPress={() => { + setVisible(false) + }} + onBackdropPress={() => { + setVisible(false) + }} + /> + + ) +} + +export default Layout diff --git a/apps/expo/src/app/user/account-settings.tsx b/apps/expo/src/app/(dashboard)/account-settings.tsx similarity index 67% rename from apps/expo/src/app/user/account-settings.tsx rename to apps/expo/src/app/(dashboard)/account-settings.tsx index 506db27e..2d8e474b 100644 --- a/apps/expo/src/app/user/account-settings.tsx +++ b/apps/expo/src/app/(dashboard)/account-settings.tsx @@ -1,18 +1,23 @@ import React, { useState } from 'react' -import { ActivityIndicator, Button, FlatList, Pressable, StyleSheet, Switch, Text, View } from 'react-native' +import { + ActivityIndicator, + FlatList, + Pressable, + StyleSheet, + Switch, + Text, + View, +} from 'react-native' import { SafeAreaView } from 'react-native-safe-area-context' import { router } from 'expo-router' -import { ClerkProvider, SignedIn, SignedOut } from '@clerk/clerk-expo' import FontAwesome5 from '@expo/vector-icons/FontAwesome5' -import AuthScreen from '~/app/auth' +import { useGlobalContext } from '~/context/global-context' import colors from '~/styles/colors' import { api } from '~/utils/api' import { FA } from '~/utils/constants' -const userId = 1 // TODO: remove when real user ID is used. - interface NavigationListItem { title: string iconName: string @@ -48,18 +53,38 @@ const tailwindClasses = { const accountItems: NavigationListItem[] = [ { title: 'Personal Data', iconName: 'user', onPress: () => router.push('/user/personal-data') }, { title: 'Achievements', iconName: 'award', onPress: () => router.push('/user/achievements') }, - { title: 'Activity History', iconName: 'history', onPress: () => router.push('/user/activity-history') }, - { title: 'Workout Progress', iconName: 'chart-pie', onPress: () => router.push('/user/workout-progress') }, + { + title: 'Activity History', + iconName: 'history', + onPress: () => router.push('/user/activity-history'), + }, + { + title: 'Workout Progress', + iconName: 'chart-pie', + onPress: () => router.push('/user/workout-progress'), + }, ] const otherItems: NavigationListItem[] = [ { title: 'Contact Us', iconName: 'envelope', onPress: () => router.push('/contact-us') }, - { title: 'Privacy Policy', iconName: 'shield-alt', onPress: () => router.push('/privacy-policy') }, + { + title: 'Privacy Policy', + iconName: 'shield-alt', + onPress: () => router.push('/privacy-policy'), + }, ] const AccountSettings = () => { - // const context = api.useUtils() - const { data: user, isLoading, isFetching } = api.user.byId.useQuery({ id: userId }) + const { userData } = useGlobalContext() + + // FIXME: + if (!userData) { + // TODO: Add a no user found component + console.log('USER DATA IS NULL, MAKE SURE YOUR DB IS RUNNING...') + return null + } + + const { data: user, isLoading, isFetching } = api.user.byId.useQuery({ id: userData.id }) const updateUserMutation = api.user.update.useMutation() const [notificationsSwitchEnabled, setNotificationsSwitchEnabled] = useState(false) @@ -75,10 +100,9 @@ const AccountSettings = () => { setNotificationsSwitchEnabled(newValue) try { await updateUserMutation.mutateAsync({ - id: userId, + id: userData.id, notificationsBanners: newValue, }) - // context.user.byId.invalidate() } catch (error) { console.error('Failed to update user settings', error) setNotificationsSwitchEnabled(!newValue) @@ -88,42 +112,55 @@ const AccountSettings = () => { if (isLoading) { return ( - + ) } if (isLoading) { return ( - + Something - + ) } return ( - - - - + + + + - - {user?.name} - Streak: {user?.streak} days + + {user?.name} + Streak: {user?.streak} days + Account ( - - + + {item.title} @@ -132,6 +169,7 @@ const AccountSettings = () => { /> + Notifications @@ -146,16 +184,21 @@ const AccountSettings = () => { /> + Other ( - - + + {item.title} diff --git a/apps/expo/src/app/(dashboard)/index.tsx b/apps/expo/src/app/(dashboard)/index.tsx new file mode 100644 index 00000000..2d3a6c1e --- /dev/null +++ b/apps/expo/src/app/(dashboard)/index.tsx @@ -0,0 +1,27 @@ +import React from 'react' +import { Text, View } from 'react-native' +import { SafeAreaView } from 'react-native-safe-area-context' +import { router } from 'expo-router' + +import Button from '~/components/ui/button/button' + +// FIXME: Dashboard start here +const Dashboard = () => { + return ( + + + Home screen + + + + + ) +} + +export default Dashboard diff --git a/apps/expo/src/app/(dashboard)/unused.tsx b/apps/expo/src/app/(dashboard)/unused.tsx new file mode 100644 index 00000000..94a8eb0f --- /dev/null +++ b/apps/expo/src/app/(dashboard)/unused.tsx @@ -0,0 +1,16 @@ +import { Text, View } from 'react-native' +import { cn } from '^/packages/ui/src/cn' + + +/** + * @depraecated + * @date 4/10/2024 + * No more development should be make here + */ +export default function Unused() { + return ( + + YOU SHOULDN'T SEE THIS + + ) +} diff --git a/apps/expo/src/app/(friends)/_layout.tsx b/apps/expo/src/app/(friends)/_layout.tsx new file mode 100644 index 00000000..b6a04515 --- /dev/null +++ b/apps/expo/src/app/(friends)/_layout.tsx @@ -0,0 +1,27 @@ +import React from 'react' +import { Tabs } from 'expo-router' + +const Layout = () => { + return ( + <> + + {/* */} + + + {/* create */} + + {/* existing */} + + + ) +} + +export default Layout diff --git a/apps/expo/src/app/(friends)/index.tsx b/apps/expo/src/app/(friends)/index.tsx new file mode 100644 index 00000000..89fa47d4 --- /dev/null +++ b/apps/expo/src/app/(friends)/index.tsx @@ -0,0 +1,18 @@ +import React from 'react' +import { Text, View } from 'react-native' +import { SafeAreaView } from 'react-native-safe-area-context' +import { router } from 'expo-router' + +import Button from '~/components/ui/button/button' + +const Friends = () => { + return ( + + + Friends screen ye + + + ) +} + +export default Friends diff --git a/apps/expo/src/app/(inbox)/_layout.tsx b/apps/expo/src/app/(inbox)/_layout.tsx new file mode 100644 index 00000000..e3c021fc --- /dev/null +++ b/apps/expo/src/app/(inbox)/_layout.tsx @@ -0,0 +1,44 @@ +import React from 'react' +import { Tabs } from 'expo-router' + +import { InboxHeader } from '~/layouts/headers/inbox-headers' + +const Layout = () => { + return ( + <> + + {/* switch nav or inbox, notif, dm and so forth */} + , + }} + /> + + , + }} + /> + + , + }} + /> + + + ) +} + +export default Layout diff --git a/apps/expo/src/app/(inbox)/direct-messages.tsx b/apps/expo/src/app/(inbox)/direct-messages.tsx new file mode 100644 index 00000000..c9e019f2 --- /dev/null +++ b/apps/expo/src/app/(inbox)/direct-messages.tsx @@ -0,0 +1,25 @@ +import React from 'react' +import { Text, View } from 'react-native' +import { SafeAreaView } from 'react-native-safe-area-context' +import { router } from 'expo-router' + +import Button from '~/components/ui/button/button' + +// FIXME: Dashboard start here +const DirectMessages = () => { + return ( + + + DirectMessages screen ye + + + + ) +} + +export default DirectMessages diff --git a/apps/expo/src/app/(inbox)/group-messages.tsx b/apps/expo/src/app/(inbox)/group-messages.tsx new file mode 100644 index 00000000..82a05e13 --- /dev/null +++ b/apps/expo/src/app/(inbox)/group-messages.tsx @@ -0,0 +1,24 @@ +import React from 'react' +import { Text, View } from 'react-native' +import { SafeAreaView } from 'react-native-safe-area-context' +import { router } from 'expo-router' + +import Button from '~/components/ui/button/button' + +const GroupMessages = () => { + return ( + + + GroupMessages screen ye + + + + ) +} + +export default GroupMessages diff --git a/apps/expo/src/app/(inbox)/index.tsx b/apps/expo/src/app/(inbox)/index.tsx new file mode 100644 index 00000000..fa2a31a6 --- /dev/null +++ b/apps/expo/src/app/(inbox)/index.tsx @@ -0,0 +1,26 @@ +import React from 'react' +import { Text, View } from 'react-native' +import { SafeAreaView } from 'react-native-safe-area-context' +import { router } from 'expo-router' + +import Button from '~/components/ui/button/button' + +// FIXME: Dashboard start here +const Inbox = () => { + return ( + + + Inbox screen ye + + + + + ) +} + +export default Inbox diff --git a/apps/expo/src/app/(tabs)/_layout.tsx b/apps/expo/src/app/(tabs)/_layout.tsx deleted file mode 100644 index 9ec17314..00000000 --- a/apps/expo/src/app/(tabs)/_layout.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import React, { useRef } from 'react' -import { Text, View } from 'react-native' -import Animated, { FadeIn, FadeOut } from 'react-native-reanimated' -import { Tabs } from 'expo-router' -import { cn } from '@/packages/ui/src/cn' -import BottomSheet from '@gorhom/bottom-sheet' -import CircleMinus from '~assets/svgs/circle-minus.svg' -import CirclePlus from '~assets/svgs/circle-plus.svg' -import HomeLogo from '~assets/svgs/home.svg' -import Profile from '~assets/svgs/profile.svg' - -import type { CustomBottomSheetModalRef } from '~/components/custom-bottom-sheet-modal' -import CustomBottomSheetModal from '~/components/custom-bottom-sheet-modal' -import CustomBottomSheet from '~/components/ui/bottom-sheet/bottom-sheet' -import colors from '~/styles/colors' - -const Layout = () => { - // const bottomSheetRef = useRef(null) - - // const handlePresentModal = () => { - // bottomSheetRef.current?.present() - // } - - // for animated dot - const AnimatedDot = ({ focused }: { focused: boolean }) => ( - - ) - - return ( - <> - - {/* */} - ( - - - - - ), - }} - /> - - {/* PLUS CIRCLE */} - { - // e.preventDefault() - // handlePresentModal() - }, - }} - options={{ - headerShown: false, - tabBarIcon: ({ size,focused }) => ( - - {/* */} - {focused ? ( - - ) : ( - - )} - - ), - }} - /> - - {/* */} - ( - - - - - ), - }} - /> - - - {/* FIXME: */} - {/* - Example Content - */} - - ) -} - -export default Layout diff --git a/apps/expo/src/app/(tabs)/index.tsx b/apps/expo/src/app/(tabs)/index.tsx deleted file mode 100644 index b9db3f1f..00000000 --- a/apps/expo/src/app/(tabs)/index.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react' -import { Text } from 'react-native' -import { SafeAreaView } from 'react-native-safe-area-context' -import { Stack } from 'expo-router' - -import RotatingBarbellIcon from '~/components/notif/RotatingBarbellIcon' -import { useGlobalContext } from '~/context/global-context' - -const Dashboard = () => { - const { userData } = useGlobalContext() - - console.log('userData', userData) - - return ( - - {/* Define pour custom header */} - {/* null, - }} - /> */} - Home screen - {userData ? ( - <> - User Data - {userData?.id} - {userData?.clerkId} - {userData?.name} - {userData?.username} - - ) : ( - - )} - - ) -} - -export default Dashboard diff --git a/apps/expo/src/app/(tabs)/one.tsx b/apps/expo/src/app/(tabs)/one.tsx deleted file mode 100644 index 4f94e21e..00000000 --- a/apps/expo/src/app/(tabs)/one.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { useRef, useState } from 'react' -import { Button as RNButton, Text, TextInput, View } from 'react-native' -import { router } from 'expo-router' -import { cn } from '@/packages/ui/src/cn' -import BottomSheet from '@gorhom/bottom-sheet' -import Button from '~/components/ui/button/button' - -import CustomBottomSheetModal from '~/components/custom-bottom-sheet-modal' -import CustomBottomSheet from '~/components/ui/bottom-sheet/bottom-sheet' -import colors from '~/styles/colors' - -export default function TabTwoScreen() { - const bottomSheetRef = useRef(null) - const [title, setTitle] = useState('Passing my data') - - const handleClosePress = () => bottomSheetRef.current?.close() - const handleOpenPress = () => bottomSheetRef.current?.expand() - - return ( - - - - - - - ) -} diff --git a/apps/expo/src/app/(tabs)/two.tsx b/apps/expo/src/app/(tabs)/two.tsx deleted file mode 100644 index 0cbb212d..00000000 --- a/apps/expo/src/app/(tabs)/two.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react' -import AccountSettings from '../user/account-settings' - - -const Page = () => { - return -} - -export default Page diff --git a/apps/expo/src/app/(workout)/_layout.tsx b/apps/expo/src/app/(workout)/_layout.tsx new file mode 100644 index 00000000..b6a04515 --- /dev/null +++ b/apps/expo/src/app/(workout)/_layout.tsx @@ -0,0 +1,27 @@ +import React from 'react' +import { Tabs } from 'expo-router' + +const Layout = () => { + return ( + <> + + {/* */} + + + {/* create */} + + {/* existing */} + + + ) +} + +export default Layout diff --git a/apps/expo/src/app/(workout)/index.tsx b/apps/expo/src/app/(workout)/index.tsx new file mode 100644 index 00000000..b7aed02b --- /dev/null +++ b/apps/expo/src/app/(workout)/index.tsx @@ -0,0 +1,19 @@ +import React from 'react' +import { Text, View } from 'react-native' +import { SafeAreaView } from 'react-native-safe-area-context' +import { router } from 'expo-router' + +import Button from '~/components/ui/button/button' + +// FIXME: Dashboard start here +const Workout = () => { + return ( + + + Workout screen ye + + + ) +} + +export default Workout diff --git a/apps/expo/src/app/_layout.tsx b/apps/expo/src/app/_layout.tsx index 5422461c..3bbf7201 100644 --- a/apps/expo/src/app/_layout.tsx +++ b/apps/expo/src/app/_layout.tsx @@ -8,22 +8,45 @@ import { TRPCProvider } from '~/utils/api' import 'expo-dev-client' import '~/styles.css' +import { useEffect } from 'react' import { View } from 'react-native' +import { GestureHandlerRootView } from 'react-native-gesture-handler' import { SafeAreaProvider } from 'react-native-safe-area-context' +import { SplashScreen } from 'expo-router' import * as SecureStore from 'expo-secure-store' import { ClerkProvider, SignedIn, SignedOut } from '@clerk/clerk-expo' +import { + IstokWeb_400Regular, + IstokWeb_400Regular_Italic, + IstokWeb_700Bold, + IstokWeb_700Bold_Italic, +} from '@expo-google-fonts/istok-web' +import { + Sora_100Thin, + Sora_200ExtraLight, + Sora_300Light, + Sora_400Regular, + Sora_500Medium, + Sora_600SemiBold, + Sora_700Bold, + Sora_800ExtraBold, +} from '@expo-google-fonts/sora' import { BottomSheetModalProvider } from '@gorhom/bottom-sheet' import AuthScreen from '~/app/auth' import GlobalContextProvider from '~/context/global-context' +import { DashboardHeader } from '~/layouts/headers/dashboard-header' +import { FriendsHeader } from '~/layouts/headers/friends-header' +import { InboxHeader } from '~/layouts/headers/inbox-headers' +import { WorkoutHeader } from '~/layouts/headers/workout-headers' const CLERK_PUBLISHABLE_KEY = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY const tokenCache = { async getToken(key: string) { try { - return await SecureStore.getItemAsync(key) + return SecureStore.getItemAsync(key) } catch (err) { console.error('Error getting token:', err) return null @@ -31,7 +54,7 @@ const tokenCache = { }, async saveToken(key: string, value: string) { try { - return await SecureStore.setItemAsync(key, value) + return SecureStore.setItemAsync(key, value) } catch (err) { console.error('Error saving token:', err) return @@ -40,16 +63,45 @@ const tokenCache = { } // Prevent the splash screen from auto-hiding before asset loading is complete. -// SplashScreen.preventAutoHideAsync() +SplashScreen.preventAutoHideAsync() // This is the main layout of the app // It wraps your pages with the providers they need export default function RootLayout() { - let [fontsLoaded] = useFonts({ + /* fonts */ + const [loaded, error] = useFonts({ Koulen_400Regular, + /* */ + IstokWeb_400Regular, + IstokWeb_400Regular_Italic, + IstokWeb_700Bold, + IstokWeb_700Bold_Italic, + /* */ + Sora_100Thin, + Sora_200ExtraLight, + Sora_300Light, + Sora_400Regular, + Sora_500Medium, + Sora_600SemiBold, + Sora_700Bold, + Sora_800ExtraBold, }) - if (!fontsLoaded) return null + // Expo Router uses Error Boundaries + // to catch errors in the navigation tree. + useEffect(() => { + if (error) throw error + }, [error]) + + useEffect(() => { + if (loaded) { + SplashScreen.hideAsync() + } + }, [loaded]) + + if (!loaded) { + return null + } const isDevelopment = process.env.NODE_ENV === 'development' @@ -74,31 +126,67 @@ export default function RootLayout() { function AppContent() { return ( - - - - - - {/* Splitter */} - - - - - + + + + + + + {/* Splitter */} + + + + + + ) } +/* */ function RootLayoutBottomNav() { - // const router = useRouter() - /* Our main navigation here (idk what is best practices here :<) */ return ( - + + ( + + + + ), + }} + /> + + {/* new workout? workout view? */} + ( + + + + ), + }} + /> + + {/* inbox */} + , + }} + /> + + {/* friends */} , + header: () => ( + + + + ), }} /> diff --git a/apps/expo/src/app/auth/index.tsx b/apps/expo/src/app/auth/index.tsx index e4b8b473..8843a326 100644 --- a/apps/expo/src/app/auth/index.tsx +++ b/apps/expo/src/app/auth/index.tsx @@ -9,11 +9,13 @@ const AuthScreen = () => { - - - BARBELL + + + + BARBELL + diff --git a/apps/expo/src/app/award/index.tsx b/apps/expo/src/app/award/index.tsx index fa52481b..f41f1f57 100644 --- a/apps/expo/src/app/award/index.tsx +++ b/apps/expo/src/app/award/index.tsx @@ -1,8 +1,7 @@ +import { Ionicons } from '@expo/vector-icons' +import { router } from 'expo-router' import React, { useState } from 'react' import { Dimensions, Image, Text, View } from 'react-native' -import { router } from 'expo-router' -import tailwind from '@/tooling/tailwind' -import { Ionicons, MaterialCommunityIcons } from '@expo/vector-icons' import { SafeAreaView } from 'react-native-safe-area-context' import Toggle from '~/components/toggle/Toggle' diff --git a/apps/expo/src/app/messages/index.tsx b/apps/expo/src/app/messages/index.tsx index b6292b06..6e988fdd 100644 --- a/apps/expo/src/app/messages/index.tsx +++ b/apps/expo/src/app/messages/index.tsx @@ -11,7 +11,7 @@ import { useGlobalContext } from '~/context/global-context' import { api } from '~/utils/api' export default function MessageView() { - const p = useLocalSearchParams() + const p = useLocalSearchParams() // TF is this? const { userData: user } = useGlobalContext() const { diff --git a/apps/expo/src/app/nav/index.tsx b/apps/expo/src/app/nav/index.tsx index 7044ef02..1e5c3497 100644 --- a/apps/expo/src/app/nav/index.tsx +++ b/apps/expo/src/app/nav/index.tsx @@ -1,8 +1,9 @@ // import type { Route } from 'expo-router' +import { router, Route } from 'expo-router' +import { Href } from 'expo-router/build/link/href' import React from 'react' import { Button, FlatList, StyleSheet } from 'react-native' import { SafeAreaView } from 'react-native-safe-area-context' -import { Route, router } from 'expo-router' const styles = StyleSheet.create({ container: { @@ -34,12 +35,17 @@ const Nav = () => { { key: 'Award', route: '/award' }, { key: 'Spotify', route: '/spotify' }, { key: 'Tracker', route: '/tracker' }, + { key: '(workout)', route: '/(workout)' }, + { key: '(inbox)', route: '/(inbox)' }, + { key: '(friends)', route: '/(friends)' }, { key: 'MuscleGroup', route: '/muscleGroup' }, ]} renderItem={({ item }) => ( ) } diff --git a/apps/expo/src/components/custom-bottom-sheet-modal.tsx b/apps/expo/src/components/custom-bottom-sheet-modal.tsx deleted file mode 100644 index 6c5d3dc2..00000000 --- a/apps/expo/src/components/custom-bottom-sheet-modal.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { forwardRef, useCallback, useMemo } from 'react' - -import { BottomSheetBackdrop, BottomSheetBackdropProps, BottomSheetModal } from '@gorhom/bottom-sheet' - -export type CustomBottomSheetModalRef = BottomSheetModal -export interface CustomBottomSheetModalProps { - children: React.ReactNode - customSnapPoints: (string | number)[] - startIndex: number - enablePanDownToClose?: boolean - renderBackdrop?: boolean -} - -// This component is a wrapper around the BottomSheetModal component from @gorhom/bottom-sheet. -// It's used to create a custom bottom sheet modal with the following features: -// - Custom snap points -// - Custom start index -// - Pan down to close -// - Backdrop -// Set up the component by passing the following props: -// - (REQUIRED) children: the content of the modal -// - (REQUIRED) customSnapPoints: an array of numbers or strings representing the snap points of the modal -// - (REQUIRED) startIndex: the index of the initial snap point of the customSnapPoints array -// - enablePanDownToClose: a boolean to enable or disable the pan down to close feature -// - renderBackdrop: a boolean to enable or disable the backdrop -// In order to make the modal pop up you will need to make a reference to the component and call the present method. -// Example: -/** -import { useRef } from 'react' -import { Button, Text, View } from 'react-native' -import type { CustomBottomSheetModalRef } from '~/components/custom-bottom-sheet-modal' -import CustomBottomSheetModal from '~/components/custom-bottom-sheet-modal' - -const Example = () => { - const bottomSheetRef = useRef(null) - const handlePresentModalPress = () => bottomSheetRef.current?.present() - - return ( - - - - Example - - - ) -} -*/ -const CustomBottomSheetModal = forwardRef( - ({ children, customSnapPoints, startIndex, enablePanDownToClose, renderBackdrop }, ref) => { - const snapPoints = useMemo(() => customSnapPoints, []) - - const backdrop = useCallback( - (props: BottomSheetBackdropProps) => , - [], - ) - - return ( - - {children} - - ) - }, -) - -export default CustomBottomSheetModal diff --git a/apps/expo/src/components/notif/gcNotifs/gcNotifs.tsx b/apps/expo/src/components/notif/gcNotifs/gcNotifs.tsx index 6fb8457e..3e177464 100644 --- a/apps/expo/src/components/notif/gcNotifs/gcNotifs.tsx +++ b/apps/expo/src/components/notif/gcNotifs/gcNotifs.tsx @@ -6,7 +6,7 @@ import Conversation from '~/components/notif/Conversation' import RotatingBarbellIcon from '~/components/notif/RotatingBarbellIcon' import { useGlobalContext } from '~/context/global-context' import { api } from '~/utils/api' -import { makeChatName } from '~/utils/makeChatName' +import { makeChatName } from '~/utils/common' function makeNullMessagePreview(chat: any, loggedInUser: string): string { return ( diff --git a/apps/expo/src/components/ui/bottom-sheet/__snapshots__/bottom-sheet.test.tsx.snap b/apps/expo/src/components/ui/bottom-sheet/__snapshots__/bottom-sheet.test.tsx.snap index ade24c00..fdbab05d 100644 --- a/apps/expo/src/components/ui/bottom-sheet/__snapshots__/bottom-sheet.test.tsx.snap +++ b/apps/expo/src/components/ui/bottom-sheet/__snapshots__/bottom-sheet.test.tsx.snap @@ -44,7 +44,7 @@ exports[`CustomBottomSheet renders correctly 1`] = ` testID="button-test" > Start Saved Workout @@ -84,7 +84,7 @@ exports[`CustomBottomSheet renders correctly 1`] = ` testID="button-test-2" > Create New Workout diff --git a/apps/expo/src/components/ui/bottom-sheet/bottom-sheet.tsx b/apps/expo/src/components/ui/bottom-sheet/bottom-sheet.tsx index df8072ed..aad6b104 100644 --- a/apps/expo/src/components/ui/bottom-sheet/bottom-sheet.tsx +++ b/apps/expo/src/components/ui/bottom-sheet/bottom-sheet.tsx @@ -1,7 +1,7 @@ import React, { forwardRef, useMemo } from 'react' -import { Button as ReactButton, StyleSheet, View } from 'react-native' +import { StyleSheet, View } from 'react-native' -import BottomSheet, { useBottomSheet } from '@gorhom/bottom-sheet' +import BottomSheet from '@gorhom/bottom-sheet' import Button from '~/components/ui/button/button' import colors from '~/styles/colors' @@ -31,8 +31,8 @@ const CustomBottomSheet = forwardRef((_, ref) => { backgroundStyle={{ backgroundColor: colors.bottomav.nav }} > - - + + ) diff --git a/apps/expo/src/components/ui/button/__snapshots__/button.test.tsx.snap b/apps/expo/src/components/ui/button/__snapshots__/button.test.tsx.snap index 49712835..cf7dcb5e 100644 --- a/apps/expo/src/components/ui/button/__snapshots__/button.test.tsx.snap +++ b/apps/expo/src/components/ui/button/__snapshots__/button.test.tsx.snap @@ -34,7 +34,7 @@ exports[`Button 1`] = ` onStartShouldSetResponder={[Function]} > Button Text diff --git a/apps/expo/src/components/ui/button/button.tsx b/apps/expo/src/components/ui/button/button.tsx index d423c5b6..e31b2f65 100644 --- a/apps/expo/src/components/ui/button/button.tsx +++ b/apps/expo/src/components/ui/button/button.tsx @@ -4,7 +4,7 @@ import { Pressable, PressableProps, Text } from 'react-native' import { cva } from 'class-variance-authority' -import { cn } from '../../../utils/cn' +import { cn } from '~/utils/cn' const buttonVariants = cva('active:opacity-30 disabled:opacity-60', { variants: { @@ -52,7 +52,7 @@ const Button = ({ value, color, size, rounded, className, children, ...props }: return ( {value && ( - + {value} )} diff --git a/apps/expo/src/components/ui/custom-bottom-sheet-modal.tsx b/apps/expo/src/components/ui/custom-bottom-sheet-modal.tsx index 4cdf4d0e..fc389813 100644 --- a/apps/expo/src/components/ui/custom-bottom-sheet-modal.tsx +++ b/apps/expo/src/components/ui/custom-bottom-sheet-modal.tsx @@ -1,4 +1,5 @@ import { forwardRef, useCallback, useMemo } from 'react' + import { BottomSheetBackdrop, BottomSheetBackdropProps, @@ -54,25 +55,23 @@ const Example = () => { ) } */ -const CustomBottomSheetModal = forwardRef< - CustomBottomSheetModalRef, - CustomBottomSheetModalProps ->( - ( - { - children, - customSnapPoints, - startIndex, - enablePanDownToClose, - renderBackdrop, - }, - ref, - ) => { + +const CustomBottomSheetModal = forwardRef( + ({ children, customSnapPoints, startIndex, enablePanDownToClose, renderBackdrop }, ref) => { const snapPoints = useMemo(() => customSnapPoints, []) const backdrop = useCallback( (props: BottomSheetBackdropProps) => ( - + ), [], ) @@ -82,6 +81,7 @@ const CustomBottomSheetModal = forwardRef< ref={ref} snapPoints={snapPoints} index={startIndex} + style={{ backgroundColor: 'transparent' }} enablePanDownToClose={enablePanDownToClose ?? true} backdropComponent={renderBackdrop ? backdrop : undefined} handleIndicatorStyle={{ backgroundColor: '#CACACA' }} diff --git a/apps/expo/src/components/ui/picker-modal/action-button/action-button.style.ts b/apps/expo/src/components/ui/picker-modal/action-button/action-button.style.ts new file mode 100644 index 00000000..c2fb7837 --- /dev/null +++ b/apps/expo/src/components/ui/picker-modal/action-button/action-button.style.ts @@ -0,0 +1,22 @@ +import { StyleSheet, TextStyle, ViewStyle } from 'react-native' + +export const _dynamicBorderStyle = (isLastItem: boolean): ViewStyle => ({ + borderBottomLeftRadius: isLastItem ? 12 : 0, + borderBottomRightRadius: isLastItem ? 12 : 0, +}) + +interface Style { + actionButtonStyle: ViewStyle + actionButtonTextStyle: TextStyle +} + +export default StyleSheet.create