diff --git a/backend/entities/clubs/followers/routes.go b/backend/entities/clubs/followers/routes.go index a89ee1f6..4b4e8fa1 100644 --- a/backend/entities/clubs/followers/routes.go +++ b/backend/entities/clubs/followers/routes.go @@ -1,8 +1,6 @@ package followers import ( - authMiddleware "github.com/GenerateNU/sac/backend/middleware/auth" - "github.com/GenerateNU/sac/backend/types" ) @@ -15,12 +13,12 @@ func ClubFollower(clubParams types.RouteParams) { clubFollowers.Get("/", clubParams.UtilityMiddleware.Paginator, clubFollowerController.GetClubFollowers) clubFollowers.Post( "/:userID", - authMiddleware.AttachExtractor(clubParams.AuthMiddleware.ClubAuthorizeById, authMiddleware.ExtractFromParams("clubID")), + clubParams.AuthMiddleware.UserAuthorizeById, clubFollowerController.CreateClubFollowing, ) clubFollowers.Delete( "/:userID", - authMiddleware.AttachExtractor(clubParams.AuthMiddleware.ClubAuthorizeById, authMiddleware.ExtractFromParams("clubID")), + clubParams.AuthMiddleware.UserAuthorizeById, clubFollowerController.DeleteClubFollowing, ) } diff --git a/frontend/lib/package.json b/frontend/lib/package.json index 185bd649..350abf78 100644 --- a/frontend/lib/package.json +++ b/frontend/lib/package.json @@ -1,6 +1,6 @@ { "name": "@generatesac/lib", - "version": "0.0.184", + "version": "0.0.186", "type": "module", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/frontend/lib/src/api/clubApi.ts b/frontend/lib/src/api/clubApi.ts index 70d9f8d3..4d6c5447 100644 --- a/frontend/lib/src/api/clubApi.ts +++ b/frontend/lib/src/api/clubApi.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { Club, + CreateClubFollower, CreateClubRequestBody, CreateClubTagsRequestBody, UpdateClubRequestBody, @@ -132,6 +133,22 @@ export const clubApi = baseApi.injectEndpoints({ return z.array(eventSchema).parse(response); }, }), + createClubFollower: builder.mutation({ + query: ({ club_id, user_id }) => ({ + url: `${CLUB_API_BASE_URL}/${club_id}/followers/${user_id}`, + method: "POST", + responseHandler: "text" + }), + invalidatesTags: ["Follower"], + }), + deleteClubFollower: builder.mutation({ + query: ({ club_id, user_id }) => ({ + url: `${CLUB_API_BASE_URL}/${club_id}/followers/${user_id}`, + method: "DELETE", + responseHandler: "text" + }), + invalidatesTags: ["Follower"], + }), clubFollowers: builder.query< User[], { id: string; queryParams?: PaginationQueryParams } @@ -154,47 +171,6 @@ export const clubApi = baseApi.injectEndpoints({ return z.array(userSchema).parse(response); }, }), - clubMembers: builder.query< - User[], - { id: string; queryParams?: PaginationQueryParams } - >({ - query: ({ id, queryParams }) => ({ - url: handleQueryParams( - `${CLUB_API_BASE_URL}/${id}/members/`, - queryParams, - ), - method: "GET", - }), - providesTags: (result) => - result - ? result.map((member) => ({ type: "User", id: member.id })) - : ["User"], - transformResponse: (response) => { - return z.array(userSchema).parse(response); - }, - }), - createClubMember: builder.mutation< - User, - { clubID: string; userID: string } - >({ - query: ({ clubID, userID }) => ({ - url: `${CLUB_API_BASE_URL}/${clubID}/members/${userID}`, - method: "POST", - }), - invalidatesTags: (result, _, { userID }) => - result ? [{ type: "User", id: userID }] : [], - }), - deleteClubMember: builder.mutation< - void, - { clubID: string; userID: string } - >({ - query: ({ clubID, userID }) => ({ - url: `${CLUB_API_BASE_URL}/${clubID}/members/${userID}`, - method: "DELETE", - }), - invalidatesTags: (result, _, { userID }) => - result ? [{ type: "User", id: userID }] : [], - }), clubPointOfContacts: builder.query({ query: (id) => ({ url: `${CLUB_API_BASE_URL}/${id}/pocs/`, diff --git a/frontend/lib/src/api/userApi.ts b/frontend/lib/src/api/userApi.ts index e91c205c..76c5cfb0 100644 --- a/frontend/lib/src/api/userApi.ts +++ b/frontend/lib/src/api/userApi.ts @@ -1,6 +1,5 @@ import { z } from "zod"; -import { Club, clubSchema } from "../types/club"; import { PaginationQueryParams } from "../types/root"; import { Tag, tagSchema } from "../types/tag"; import { @@ -11,6 +10,7 @@ import { userSchema, } from "../types/user"; import { baseApi, handleQueryParams } from "./base"; +import { Club, clubSchema } from "../types"; const USER_API_BASE_URL = "/users"; @@ -81,54 +81,6 @@ export const userApi = baseApi.injectEndpoints({ }), invalidatesTags: (_result, _, id) => [{ type: "User", id }], }), - userFollowing: builder.query({ - query: (id) => ({ - url: `${USER_API_BASE_URL}/${id}/follower/`, - method: "GET", - }), - providesTags: (result, _, id) => - result - ? [{ type: "Follower", id }, "Club"] - : [{ type: "Follower", id }], - transformResponse: (response) => { - return z.array(clubSchema).parse(response); - }, - }), - createUserFollowing: builder.mutation< - void, - { userID: string; clubID: string } - >({ - query: ({ userID, clubID }) => ({ - url: `${USER_API_BASE_URL}/${userID}/follower/${clubID}`, - method: "POST", - }), - invalidatesTags: (_result, _, { userID }) => [ - { type: "Follower", id: userID }, - ], - }), - deleteUserFollowing: builder.mutation< - void, - { userID: string; clubID: string } - >({ - query: ({ userID, clubID }) => ({ - url: `${USER_API_BASE_URL}/${userID}/follower/${clubID}`, - method: "DELETE", - }), - invalidatesTags: (_result, _, { userID }) => [ - { type: "Follower", id: userID }, - ], - }), - userMembership: builder.query({ - query: (id) => ({ - url: `${USER_API_BASE_URL}/${id}/member/`, - method: "GET", - }), - providesTags: (result, _, id) => - result ? [{ type: "Member", id }, "Club"] : [{ type: "Member", id }], - transformResponse: (response) => { - return z.array(clubSchema).parse(response); - }, - }), userTags: builder.query({ query: () => ({ url: `${USER_API_BASE_URL}/tags/`, @@ -159,5 +111,14 @@ export const userApi = baseApi.injectEndpoints({ }), invalidatesTags: (_result, _, id) => [{ type: "Tag", id }], }), + getUserFollowing: builder.query({ + query: (id) => ({ + url: `${USER_API_BASE_URL}/${id}/follower`, + method: "GET", + }), + transformResponse: (response) => { + return z.array(clubSchema).parse(response); + }, + }) }), }); diff --git a/frontend/lib/src/types/club.ts b/frontend/lib/src/types/club.ts index 20e871cf..cc67c8a7 100644 --- a/frontend/lib/src/types/club.ts +++ b/frontend/lib/src/types/club.ts @@ -42,6 +42,11 @@ const clubSchemaIntermediate = z.object({ recruitment: recruitmentSchema.optional(), }); +const createClubFollowerSchema = z.object({ + club_id: z.string().uuid(), + user_id: z.string().uuid(), +}); + export const clubSchema = clubSchemaIntermediate.merge(rootModelSchema); // Types: @@ -50,4 +55,5 @@ export type UpdateClubRequestBody = z.infer; export type CreateClubTagsRequestBody = z.infer< typeof createClubTagsRequestBodySchema >; +export type CreateClubFollower = z.infer; export type Club = z.infer; diff --git a/frontend/mobile/package.json b/frontend/mobile/package.json index 74efa44e..97789045 100644 --- a/frontend/mobile/package.json +++ b/frontend/mobile/package.json @@ -25,7 +25,7 @@ "@fortawesome/free-solid-svg-icons": "^6.5.2", "@fortawesome/react-fontawesome": "^0.2.2", "@fortawesome/react-native-fontawesome": "^0.3.2", - "@generatesac/lib": "0.0.184", + "@generatesac/lib": "0.0.186", "@gorhom/bottom-sheet": "^4.6.3", "@hookform/resolvers": "^3.4.2", "@react-native-async-storage/async-storage": "^1.23.1", diff --git a/frontend/mobile/src/app/app/(tabs)/_layout.tsx b/frontend/mobile/src/app/app/(tabs)/_layout.tsx index 174ec3a6..03ae3b51 100644 --- a/frontend/mobile/src/app/app/(tabs)/_layout.tsx +++ b/frontend/mobile/src/app/app/(tabs)/_layout.tsx @@ -49,17 +49,6 @@ const Layout = () => { backgroundColor: 'white' }} > - - TabBarLabel({ focused, title: 'Home' }), - tabBarIcon: ({ focused }) => - TabBarIcon({ focused, icon: faHouse }) - }} - /> { - return <>; -}; - -export default HomePage; diff --git a/frontend/mobile/src/app/app/(tabs)/profile.tsx b/frontend/mobile/src/app/app/(tabs)/profile.tsx index 06275c0f..bcf67b4f 100644 --- a/frontend/mobile/src/app/app/(tabs)/profile.tsx +++ b/frontend/mobile/src/app/app/(tabs)/profile.tsx @@ -62,7 +62,7 @@ const ProfilePage = () => { - router.push('/app/user/detail/')} icon={faUser} text="Edit Profile" @@ -71,6 +71,11 @@ const ProfilePage = () => { icon={faHeart} onPress={() => router.push('/app/user/interest/')} text="Edit Interests" + /> */} + router.push('/app/user/following/')} + text="Following" /> { const styles = StyleSheet.create({ container: { - flex: 1, flexDirection: 'column', alignItems: 'flex-start', justifyContent: 'flex-start', diff --git a/frontend/mobile/src/app/app/club/[id].tsx b/frontend/mobile/src/app/app/club/[id].tsx index 3a6b5f38..b013f933 100644 --- a/frontend/mobile/src/app/app/club/[id].tsx +++ b/frontend/mobile/src/app/app/club/[id].tsx @@ -33,6 +33,8 @@ import { EventCardList } from '../../design-system/components/EventCard/EventCar import PageError from '../../design-system/components/PageError/PageError'; import { Description } from '../event/components/description'; import ClubPageSkeleton from './components/skeleton'; +import { clubApi } from '@generatesac/lib'; +import { setUserFollowing } from '@/src/store/slices/userSlice'; const color: SACColors = 'darkRed'; @@ -47,13 +49,33 @@ const ClubPage = () => { const bottomSheet = useRef(null); const club = useAppSelector((state) => state.club); + const { id: user_id, following } = useAppSelector((state) => state.user) const dispatch = useAppDispatch(); - const { setRetriggerFetch, apiLoading, apiError } = useClub(); + const { setRetriggerFetch, apiLoading, apiError } = useClub(id); + const [followClub] = clubApi.useCreateClubFollowerMutation(); + const [unFollowClub] = clubApi.useDeleteClubFollowerMutation(); + + const clubFollowed = following.includes(id as string); - useEffect(() => { - dispatch(setClubId(id)); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const handleUserFollow = (follow: boolean) => { + if (id) { + if (follow) { + followClub({ club_id: id, user_id }) + .then(({ error }) => { + if (!error) { + dispatch(setUserFollowing([...following, id])); + } + }) + } else { + unFollowClub({ club_id: id, user_id }) + .then(({ error }) => { + if (!error) { + dispatch(setUserFollowing(following.filter(clubId => clubId !== id))); + } + }) + } + } + }; const headerAnimatedStyle = useAnimatedStyle(() => { return { @@ -92,7 +114,7 @@ const ClubPage = () => { scrollEventThrottle={16} ref={scrollRef} > - {apiLoading ? ( + {apiLoading || club.id !== id ? ( ) : apiError ? ( @@ -119,8 +141,9 @@ const ClubPage = () => { color={color} size="sm" variant="standardButton" + onPress={() => handleUserFollow(!clubFollowed)} > - Follow + {clubFollowed ? 'Unfollow' : 'Follow'} diff --git a/frontend/mobile/src/app/app/user/_layout.tsx b/frontend/mobile/src/app/app/user/_layout.tsx index be0d2d5e..ad2ab724 100644 --- a/frontend/mobile/src/app/app/user/_layout.tsx +++ b/frontend/mobile/src/app/app/user/_layout.tsx @@ -5,13 +5,24 @@ import { Stack } from 'expo-router'; const Layout = () => { return ( - + + ); }; diff --git a/frontend/mobile/src/app/app/user/following.tsx b/frontend/mobile/src/app/app/user/following.tsx new file mode 100644 index 00000000..2f62a081 --- /dev/null +++ b/frontend/mobile/src/app/app/user/following.tsx @@ -0,0 +1,79 @@ +import { useAppDispatch, useAppSelector } from "@/src/store/store"; +import { userApi } from "@generatesac/lib"; +import { useEffect, useState } from "react"; +import PageError from "../../design-system/components/PageError/PageError"; +import { Arrow, Box, Button, Text } from "../../design-system"; +import ClubCardStandardSkeleton from "../../design-system/components/ClubCard/Skeletons/ClubCardStandardSkeleton"; +import { Clubcard } from "../../design-system/components/ClubCard/ClubCard"; +import { SafeAreaView } from "react-native"; +import { GlobalLayout } from "../../design-system/components/GlobalLayout/GlobalLayout"; +import { Stack, router } from "expo-router"; + +const Following = () => { + const { id, following } = useAppSelector((state) => state.user) + const [getFollowers, { data, isLoading, error }] = userApi.useLazyGetUserFollowingQuery(); + + useEffect(() => { + getFollowers(id) + }, [following]) + + if (error) { + return ( + {}}/> + ) + } + + return ( + <> + ( + Clubs you Follow + ), + headerTransparent: true, + headerShown: true, + headerLeft: () => , + }} + /> + + + + {isLoading || !data ? + + + + + + : + + {data.map((club) => { + if (club.name !== 'SAC') { + return ( + + ) + } + })} + + + + + } + + + + + ); +}; + +export default Following; \ No newline at end of file diff --git a/frontend/mobile/src/app/auth/callback.tsx b/frontend/mobile/src/app/auth/callback.tsx index 908c8114..fde700f3 100644 --- a/frontend/mobile/src/app/auth/callback.tsx +++ b/frontend/mobile/src/app/auth/callback.tsx @@ -3,11 +3,11 @@ import { Image } from 'react-native'; import { router, useLocalSearchParams } from 'expo-router'; -import { OAuthCallbackRequestQueryParams, authApi } from '@generatesac/lib'; +import { Club, OAuthCallbackRequestQueryParams, authApi, userApi } from '@generatesac/lib'; import Loading from '@/src/assets/gif/loading.gif'; import { setLoggedIn } from '@/src/store/slices/globalSlice'; -import { setUser } from '@/src/store/slices/userSlice'; +import { setUser, setUserFollowing } from '@/src/store/slices/userSlice'; import { useAppDispatch, useAppSelector } from '@/src/store/store'; import { Box, Text } from '../design-system'; @@ -15,17 +15,34 @@ import { Box, Text } from '../design-system'; const OAuthCallback = () => { const params = useLocalSearchParams(); const [oAuthCallback] = authApi.useLazyCallbackQuery(); + const [getUserFollowing] = userApi.useLazyGetUserFollowingQuery(); const dispatch = useAppDispatch(); const { accessToken } = useAppSelector((state) => state.global); + const parseUserFollowing = (clubs: Club[]) => { + return clubs.filter((club) => club.name !== 'SAC').map((club) => club.id); + } + useEffect(() => { const { code, session_state, state } = params as OAuthCallbackRequestQueryParams; if (code || session_state || state) { oAuthCallback({ code, session_state, state }).then( - ({ data, error }) => { + async ({ data, error }) => { if (data) { + // Set the user: dispatch(setUser(data)); + + // Retrieve their following: + await getUserFollowing(data.id) + .then(({ data: followingData }) => { + if (followingData) { + const following = parseUserFollowing(followingData); + dispatch(setUserFollowing(following)); + } + }); + + // Set the user as logged in and redirect to the app: dispatch(setLoggedIn(true)); router.push('/app/'); } diff --git a/frontend/mobile/src/app/auth/index.tsx b/frontend/mobile/src/app/auth/index.tsx index afbd57a2..55c60a15 100644 --- a/frontend/mobile/src/app/auth/index.tsx +++ b/frontend/mobile/src/app/auth/index.tsx @@ -17,6 +17,7 @@ const WelcomePage = () => { const handleLogin = () => { login().then(async ({ data }) => { if (data) { + console.log(data.sac_session); await Linking.openURL(data.redirect_uri); dispatch(setAccessToken(data.sac_session)); } diff --git a/frontend/mobile/src/app/design-system/components/ClubCard/ClubCard.tsx b/frontend/mobile/src/app/design-system/components/ClubCard/ClubCard.tsx new file mode 100644 index 00000000..79b499cf --- /dev/null +++ b/frontend/mobile/src/app/design-system/components/ClubCard/ClubCard.tsx @@ -0,0 +1,36 @@ +import { Tag } from "@generatesac/lib"; +import ClubCardStandard from "./Variants/ClubCardStandard"; + +export interface ClubCardProps { + id: string; + logo: string; + name: string; + tags?: Tag[]; + variant?: 'standard' +} + +export const Clubcard = ({ + id, + logo, + name, + variant = 'standard', +}: ClubCardProps) => { + switch (variant) { + case 'standard': + return ( + + ); + default: + return ( + + ); + } +}; diff --git a/frontend/mobile/src/app/design-system/components/ClubCard/Skeletons/ClubCardStandardSkeleton.tsx b/frontend/mobile/src/app/design-system/components/ClubCard/Skeletons/ClubCardStandardSkeleton.tsx new file mode 100644 index 00000000..6dd8f398 --- /dev/null +++ b/frontend/mobile/src/app/design-system/components/ClubCard/Skeletons/ClubCardStandardSkeleton.tsx @@ -0,0 +1,47 @@ +import { Skeleton } from "@rneui/base"; +import { createStyles } from "../../../theme"; +import { Box } from "../../Box/Box"; +import { Colors } from "../../../shared/colors"; + +const ClubCardStandardSkeleton = () => { + return ( + + + + + + + ); +} + +const styles = createStyles({ + cardContainer: { + shadowColor: 'black', + shadowOffset: { width: 0, height: 1 }, + shadowOpacity: 0.1, + shadowRadius: 2, + backgroundColor: 'white', + borderRadius: 'md', + }, + cardSubContainer: { + width: '100%', + borderRadius: 'md', + padding: 'm', + backgroundColor: 'white', + flexDirection: 'row', + alignItems: 'center', + gap: 's', + overflow: 'hidden' + } +}); + +export default ClubCardStandardSkeleton; \ No newline at end of file diff --git a/frontend/mobile/src/app/design-system/components/ClubCard/Variants/ClubCardStandard.tsx b/frontend/mobile/src/app/design-system/components/ClubCard/Variants/ClubCardStandard.tsx new file mode 100644 index 00000000..5979dd97 --- /dev/null +++ b/frontend/mobile/src/app/design-system/components/ClubCard/Variants/ClubCardStandard.tsx @@ -0,0 +1,54 @@ +import { TouchableOpacity } from "react-native-gesture-handler"; +import { createStyles } from "../../../theme"; +import { Box } from "../../Box/Box"; +import { ClubCardProps } from "../ClubCard"; +import { Text } from "../../Text/Text"; +import { ClubIcon } from "../../ClubIcon/ClubIcon"; +import { router } from "expo-router"; +import { useAppDispatch } from "@/src/store/store"; +import { resetEvent } from "@/src/store/slices/eventSlice"; + +const ClubCardStandard = ({ logo, name, id }: ClubCardProps) => { + const dispatch = useAppDispatch(); + + return ( + + { + dispatch(resetEvent()); + router.navigate(`/app/club/${id}`) + }} + > + + + + {name.length > 28 ? name.slice(0, 28).trim() + '...' : name} + + + + + ) +} + +const styles = createStyles({ + cardContainer: { + shadowColor: 'black', + shadowOffset: { width: 0, height: 1 }, + shadowOpacity: 0.1, + shadowRadius: 2, + backgroundColor: 'white', + borderRadius: 'md', + width: '100%', + }, + cardSubContainer: { + width: '100%', + borderRadius: 'md', + padding: 'm', + backgroundColor: 'white', + flexDirection: 'row', + alignItems: 'center', + gap: 's', + } +}); + +export default ClubCardStandard; \ No newline at end of file diff --git a/frontend/mobile/src/app/design-system/components/ClubIcon/ClubIcon.tsx b/frontend/mobile/src/app/design-system/components/ClubIcon/ClubIcon.tsx index 5a9280cb..fa90f870 100644 --- a/frontend/mobile/src/app/design-system/components/ClubIcon/ClubIcon.tsx +++ b/frontend/mobile/src/app/design-system/components/ClubIcon/ClubIcon.tsx @@ -4,12 +4,13 @@ import { Avatar } from '@rneui/themed'; interface ClubIconProps { imageUrl: string; + size?: number; } -export const ClubIcon: React.FC = ({ imageUrl }) => { +export const ClubIcon: React.FC = ({ imageUrl, size = 77 }) => { return ( { return ( - + {children} ); diff --git a/frontend/mobile/src/hooks/useClub.ts b/frontend/mobile/src/hooks/useClub.ts index d1ec9707..57893162 100644 --- a/frontend/mobile/src/hooks/useClub.ts +++ b/frontend/mobile/src/hooks/useClub.ts @@ -5,7 +5,7 @@ import { clubApi } from '@generatesac/lib'; import { setClub, setClubEvents, setClubTags } from '../store/slices/clubSlice'; import { useAppDispatch, useAppSelector } from '../store/store'; -const useClub = () => { +const useClub = (id?: string) => { const [retriggerFetch, setRetriggerFetch] = useState(false); const [getClub, { isLoading: clubLoading, error: clubError }] = @@ -21,9 +21,11 @@ const useClub = () => { const apiLoading = clubLoading || tagsLoading || eventsLoading; const apiError = clubError || tagsError || eventsError; + const idToFetch = id ?? clubId; + useEffect(() => { - if (clubId !== '') { - getClub(clubId).then(({ data: clubData }) => { + if (idToFetch !== '') { + getClub(idToFetch).then(({ data: clubData }) => { if (clubData) { dispatch(setClub(clubData)); } @@ -35,7 +37,7 @@ const useClub = () => { } }); - getEvents({ id: clubId }).then(({ data: eventData }) => { + getEvents({ id: idToFetch }).then(({ data: eventData }) => { if (eventData) { const sortedEvents = [...eventData]; sortedEvents.sort((a, b) => { @@ -54,7 +56,7 @@ const useClub = () => { }); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [retriggerFetch, clubId]); + }, [retriggerFetch, idToFetch]); return { setRetriggerFetch, diff --git a/frontend/mobile/src/store/slices/userSlice.ts b/frontend/mobile/src/store/slices/userSlice.ts index 0f5528bd..c9fcbe54 100644 --- a/frontend/mobile/src/store/slices/userSlice.ts +++ b/frontend/mobile/src/store/slices/userSlice.ts @@ -1,13 +1,20 @@ import { User } from '@generatesac/lib'; import { createSlice } from '@reduxjs/toolkit'; -const initialState: User = { +type UserState = { + following: string[]; + rsvps: string[]; +} + +const initialState: User & UserState = { id: '', email: '', created_at: '', updated_at: '', name: '', - role: 'student' + role: 'student', + following: [], + rsvps: [], }; export const userSlice = createSlice({ @@ -26,9 +33,15 @@ export const userSlice = createSlice({ state.name = action.payload.name; state.role = action.payload.role; }, - resetUser: () => initialState + resetUser: () => initialState, + setUserFollowing : (state, action) => { + state.following = action.payload; + }, + setUserRSVPs : (state, action) => { + state.rsvps = action.payload; + } } }); -export const { setUser, resetUser } = userSlice.actions; +export const { setUser, resetUser, setUserFollowing, setUserRSVPs } = userSlice.actions; export default userSlice.reducer; diff --git a/frontend/mobile/yarn.lock b/frontend/mobile/yarn.lock index 065c593e..a164934e 100644 --- a/frontend/mobile/yarn.lock +++ b/frontend/mobile/yarn.lock @@ -1402,10 +1402,10 @@ humps "^2.0.1" prop-types "^15.7.2" -"@generatesac/lib@0.0.184": - version "0.0.184" - resolved "https://registry.yarnpkg.com/@generatesac/lib/-/lib-0.0.184.tgz#1a024934e05e69685a46bfcfe388fdf8b4809226" - integrity sha512-btRNxaCH7ovI9WCRt/0fsaUTW2mWzhgDZwBC6fDmIH929bo6etX477jPAdtBJXAWxMz/RIYvIAuLRda8qma/oA== +"@generatesac/lib@0.0.186": + version "0.0.186" + resolved "https://registry.yarnpkg.com/@generatesac/lib/-/lib-0.0.186.tgz#2e77a197b8fc621ec3ec07ee990d9acc3d867fa9" + integrity sha512-CPZsejA4K/3/uk0E1Al1vahtYh7bRLKt2Fn8qoOA+Rq1jZKUbkp6NVO+2iDz+yyZfA2UwpSUhbzjKnE/YbTuGg== dependencies: "@reduxjs/toolkit" "^2.2.3" react "^18.2.0"