diff --git a/apps/admin/src/middleware.ts b/apps/admin/src/middleware.ts
index ef32070..0081674 100644
--- a/apps/admin/src/middleware.ts
+++ b/apps/admin/src/middleware.ts
@@ -34,7 +34,6 @@ export default clerkMiddleware(async (auth, req) => {
// For API routes, let the route handler check auth
// This allows API routes to handle both web sessions and mobile Bearer tokens
if (isApiRoute(req)) {
- log.debug("API route, auth will be checked in handler");
return;
}
diff --git a/apps/mobile/src/app/(tabs)/index.tsx b/apps/mobile/src/app/(tabs)/index.tsx
index 0933f02..9ae79de 100644
--- a/apps/mobile/src/app/(tabs)/index.tsx
+++ b/apps/mobile/src/app/(tabs)/index.tsx
@@ -42,6 +42,18 @@ import {
const CALORIE_GOAL = 2000;
const WATER_GOAL = 2000;
const WORKOUT_GOAL = 3;
+const MOTIVATION_KEY_PREFIX = "home-motivation";
+
+const getRandomMotivation = () => {
+ const messages = [
+ "Let's crush it today! 💪",
+ "Ready to level up? 🔥",
+ "You've got this! âš¡",
+ "Time to shine! ✨",
+ "Let's make it happen! 🚀",
+ ];
+ return messages[Math.floor(Math.random() * messages.length)];
+};
export default function HomeScreen() {
const { user } = useUser();
@@ -56,6 +68,9 @@ export default function HomeScreen() {
const [scanFoodModalVisible, setScanFoodModalVisible] = useState(false);
const [calories, setCalories] = useState(0);
const [waterIntake, setWaterIntake] = useState(0);
+ const [motivationalMessage, setMotivationalMessage] = useState(
+ "Let's crush it today! 💪",
+ );
const caloriesBounce = useRef(new Animated.Value(1)).current;
const waterBounce = useRef(new Animated.Value(1)).current;
@@ -80,17 +95,6 @@ export default function HomeScreen() {
return "Good Evening";
};
- const getMotivationalMessage = () => {
- const messages = [
- "Let's crush it today! 💪",
- "Ready to level up? 🔥",
- "You've got this! âš¡",
- "Time to shine! ✨",
- "Let's make it happen! 🚀",
- ];
- return messages[Math.floor(Math.random() * messages.length)];
- };
-
const handleSaveMeal = (meal: {
type: string;
name: string;
@@ -145,6 +149,39 @@ export default function HomeScreen() {
await AsyncStorage.removeItem(`water_${today}`);
};
+ useEffect(() => {
+ const loadDailyMotivation = async () => {
+ const today = new Date().toISOString().split("T")[0];
+ const storageKey = `${MOTIVATION_KEY_PREFIX}_${user?.id || "guest"}`;
+ const storedValue = await AsyncStorage.getItem(storageKey);
+
+ if (storedValue) {
+ try {
+ const parsed = JSON.parse(storedValue) as {
+ date: string;
+ message: string;
+ };
+
+ if (parsed.date === today && parsed.message) {
+ setMotivationalMessage(parsed.message);
+ return;
+ }
+ } catch {
+ // Ignore invalid local value and regenerate
+ }
+ }
+
+ const nextMessage = getRandomMotivation();
+ setMotivationalMessage(nextMessage);
+ await AsyncStorage.setItem(
+ storageKey,
+ JSON.stringify({ date: today, message: nextMessage }),
+ );
+ };
+
+ loadDailyMotivation();
+ }, [user?.id]);
+
useEffect(() => {
const loadPersistedData = async () => {
const today = new Date().toDateString();
@@ -238,7 +275,7 @@ export default function HomeScreen() {
{user?.firstName || "Champion"}
- {getMotivationalMessage()}
+ {motivationalMessage}
diff --git a/apps/mobile/src/app/_layout.tsx b/apps/mobile/src/app/_layout.tsx
index 598903a..2f5afc0 100644
--- a/apps/mobile/src/app/_layout.tsx
+++ b/apps/mobile/src/app/_layout.tsx
@@ -11,6 +11,7 @@ import { StatisticsProvider } from "../contexts/StatisticsContext";
import { FitnessGoalsProvider } from "../contexts/FitnessGoalsContext";
import { RecommendationsProvider } from "../contexts/RecommendationsContext";
import { NotificationsProvider } from "../contexts/NotificationsContext";
+import { MembershipProvider } from "../contexts/MembershipContext";
import { queryClient } from "../lib/query-client";
import log from "../utils/logger";
@@ -180,11 +181,13 @@ export default function RootLayout() {
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/apps/mobile/src/contexts/MembershipContext.tsx b/apps/mobile/src/contexts/MembershipContext.tsx
new file mode 100644
index 0000000..094c4c8
--- /dev/null
+++ b/apps/mobile/src/contexts/MembershipContext.tsx
@@ -0,0 +1,96 @@
+import React, {
+ createContext,
+ useCallback,
+ useContext,
+ useEffect,
+ useMemo,
+ useState,
+ type ReactNode,
+} from "react";
+import { useAuth, useUser } from "@clerk/clerk-expo";
+import {
+ getCurrentMembershipFeaturesFromServer,
+ type MembershipFeatures,
+ type MembershipType,
+} from "../api/membership";
+import log from "../utils/logger";
+
+const BASIC_FEATURES: MembershipFeatures = {
+ recommendationsPerMonth: 1,
+ hydrationTracking: false,
+ nutritionTracking: false,
+ advancedStatistics: false,
+};
+
+interface MembershipContextValue {
+ membershipType: MembershipType;
+ features: MembershipFeatures;
+ loading: boolean;
+ refreshMembership: () => Promise;
+}
+
+const MembershipContext = createContext(
+ undefined,
+);
+
+export function MembershipProvider({ children }: { children: ReactNode }) {
+ const { user } = useUser();
+ const { getToken, isSignedIn } = useAuth();
+ const [membershipType, setMembershipType] = useState("basic");
+ const [features, setFeatures] = useState(BASIC_FEATURES);
+ const [loading, setLoading] = useState(true);
+
+ const loadMembership = useCallback(async () => {
+ if (!isSignedIn || !user?.id) {
+ setMembershipType("basic");
+ setFeatures(BASIC_FEATURES);
+ setLoading(false);
+ return;
+ }
+
+ try {
+ setLoading(true);
+ const token = await getToken();
+ const result = await getCurrentMembershipFeaturesFromServer(token);
+ setMembershipType(result.membershipType);
+ setFeatures(result.features);
+ } catch (error) {
+ log.error("Failed to load membership", error, { userId: user.id });
+ setMembershipType("basic");
+ setFeatures(BASIC_FEATURES);
+ } finally {
+ setLoading(false);
+ }
+ }, [isSignedIn, user?.id]);
+
+ useEffect(() => {
+ loadMembership();
+ }, [loadMembership]);
+
+ const value = useMemo(
+ () => ({
+ membershipType,
+ features,
+ loading,
+ refreshMembership: loadMembership,
+ }),
+ [membershipType, features, loading, loadMembership],
+ );
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useMembershipContext(): MembershipContextValue {
+ const context = useContext(MembershipContext);
+ if (!context) {
+ throw new Error(
+ "useMembershipContext must be used within MembershipProvider",
+ );
+ }
+
+ return context;
+}
diff --git a/apps/mobile/src/hooks/useMembership.ts b/apps/mobile/src/hooks/useMembership.ts
index 47ff848..2d97c31 100644
--- a/apps/mobile/src/hooks/useMembership.ts
+++ b/apps/mobile/src/hooks/useMembership.ts
@@ -1,76 +1,5 @@
-import { useAuth, useUser } from "@clerk/clerk-expo";
-import { useEffect, useState } from "react";
-import {
- getCurrentMembershipFeaturesFromServer,
- type MembershipFeatures,
- type MembershipType,
-} from "../api/membership";
-import log from "../utils/logger";
+import { useMembershipContext } from "../contexts/MembershipContext";
-const BASIC_FEATURES: MembershipFeatures = {
- recommendationsPerMonth: 1,
- hydrationTracking: false,
- nutritionTracking: false,
- advancedStatistics: false,
-};
-
-interface UseMembershipResult {
- membershipType: MembershipType;
- features: MembershipFeatures;
- loading: boolean;
-}
-
-export function useMembership(): UseMembershipResult {
- const { user } = useUser();
- const { getToken, isSignedIn } = useAuth();
- const [membershipType, setMembershipType] = useState("basic");
- const [features, setFeatures] = useState(BASIC_FEATURES);
- const [loading, setLoading] = useState(true);
-
- useEffect(() => {
- let isMounted = true;
-
- const loadMembership = async () => {
- if (!isSignedIn || !user?.id) {
- if (isMounted) {
- setMembershipType("basic");
- setFeatures(BASIC_FEATURES);
- setLoading(false);
- }
- return;
- }
-
- try {
- setLoading(true);
- const token = await getToken();
- const result = await getCurrentMembershipFeaturesFromServer(token);
- if (isMounted) {
- setMembershipType(result.membershipType);
- setFeatures(result.features);
- }
- } catch (error) {
- log.error("Failed to load membership", error, { userId: user.id });
- if (isMounted) {
- setMembershipType("basic");
- setFeatures(BASIC_FEATURES);
- }
- } finally {
- if (isMounted) {
- setLoading(false);
- }
- }
- };
-
- loadMembership();
-
- return () => {
- isMounted = false;
- };
- }, [isSignedIn, user?.id, getToken]);
-
- return {
- membershipType,
- features,
- loading,
- };
+export function useMembership() {
+ return useMembershipContext();
}