@umituz/react-native-ai-generation-content 1.26.14 → 1.26.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/package.json +1 -1
  2. package/src/domains/love-message/domain/constants.ts +162 -0
  3. package/src/domains/love-message/domain/types.ts +58 -0
  4. package/src/domains/love-message/index.ts +37 -0
  5. package/src/domains/love-message/infrastructure/persistence/PartnerProfileRepository.ts +52 -0
  6. package/src/domains/love-message/infrastructure/prompts/messagePromptBuilder.ts +109 -0
  7. package/src/domains/love-message/infrastructure/services/LoveMessageService.ts +35 -0
  8. package/src/domains/love-message/presentation/components/CategoryGrid.tsx +88 -0
  9. package/src/domains/love-message/presentation/components/DetailsInput.tsx +74 -0
  10. package/src/domains/love-message/presentation/components/ExploreHeader.tsx +67 -0
  11. package/src/domains/love-message/presentation/components/FieldInput.tsx +83 -0
  12. package/src/domains/love-message/presentation/components/GeneratorHeader.tsx +88 -0
  13. package/src/domains/love-message/presentation/components/LoveMessageHeroSection.tsx +114 -0
  14. package/src/domains/love-message/presentation/components/MessageListItem.tsx +77 -0
  15. package/src/domains/love-message/presentation/components/MessageResult.tsx +105 -0
  16. package/src/domains/love-message/presentation/components/PartnerInput.tsx +78 -0
  17. package/src/domains/love-message/presentation/components/ProgressDots.tsx +48 -0
  18. package/src/domains/love-message/presentation/components/StepDetails.tsx +23 -0
  19. package/src/domains/love-message/presentation/components/StepPartner.tsx +116 -0
  20. package/src/domains/love-message/presentation/components/StepVibe.tsx +30 -0
  21. package/src/domains/love-message/presentation/components/ToneSelector.tsx +100 -0
  22. package/src/domains/love-message/presentation/components/TrendingSection.tsx +130 -0
  23. package/src/domains/love-message/presentation/components/TypeSelector.tsx +99 -0
  24. package/src/domains/love-message/presentation/hooks/useLoveMessageGenerator.ts +114 -0
  25. package/src/domains/love-message/presentation/hooks/usePartnerProfile.ts +43 -0
  26. package/src/domains/love-message/presentation/navigation/LoveMessageStack.tsx +39 -0
  27. package/src/domains/love-message/presentation/screens/LoveMessageExploreScreen.tsx +53 -0
  28. package/src/domains/love-message/presentation/screens/LoveMessageGeneratorScreen.tsx +169 -0
  29. package/src/domains/love-message/presentation/screens/MessageListScreen.tsx +127 -0
  30. package/src/domains/love-message/presentation/screens/PartnerProfileScreen.tsx +119 -0
  31. package/src/domains/scenarios/domain/scenario.types.ts +49 -0
  32. package/src/domains/scenarios/index.ts +5 -0
  33. package/src/index.ts +1 -0
@@ -0,0 +1,116 @@
1
+ /**
2
+ * StepPartner Component
3
+ */
4
+
5
+ import React from "react";
6
+ import { View, Pressable, Switch, StyleSheet, ImageBackground } from "react-native";
7
+ import {
8
+ AtomicText,
9
+ AtomicIcon,
10
+ useAppDesignTokens,
11
+ } from "@umituz/react-native-design-system";
12
+ import { useLocalization } from "@umituz/react-native-localization";
13
+ import { PartnerInput } from "./PartnerInput";
14
+
15
+ interface StepPartnerProps {
16
+ partnerName: string;
17
+ setPartnerName: (name: string) => void;
18
+ useProfile: boolean;
19
+ setUseProfile: (val: boolean) => void;
20
+ hasProfile: boolean;
21
+ onEditProfile: () => void;
22
+ }
23
+
24
+ export const StepPartner: React.FC<StepPartnerProps> = ({
25
+ partnerName,
26
+ setPartnerName,
27
+ useProfile,
28
+ setUseProfile,
29
+ hasProfile,
30
+ onEditProfile,
31
+ }) => {
32
+ const tokens = useAppDesignTokens();
33
+ const { t } = useLocalization();
34
+
35
+ return (
36
+ <View>
37
+ <PartnerInput value={partnerName} onChangeText={setPartnerName} />
38
+
39
+ <Pressable
40
+ onPress={onEditProfile}
41
+ style={[styles.profileCard, { backgroundColor: tokens.colors.surfaceSecondary }]}
42
+ >
43
+ <View style={styles.profileLeft}>
44
+ <View style={[styles.profileIconCircle, { backgroundColor: `${tokens.colors.primary}20` }]}>
45
+ <AtomicIcon name="heart" color="primary" size="sm" />
46
+ </View>
47
+ <View>
48
+ <AtomicText type="labelMedium" color="textPrimary">
49
+ {hasProfile ? t("loveMessage.editPartnerProfile") : t("loveMessage.partnerProfile.title")}
50
+ </AtomicText>
51
+ <AtomicText type="bodySmall" color="textTertiary">
52
+ {t("loveMessage.partnerProfile.subtitle")}
53
+ </AtomicText>
54
+ </View>
55
+ </View>
56
+ <Switch
57
+ value={useProfile}
58
+ onValueChange={setUseProfile}
59
+ trackColor={{ false: tokens.colors.borderLight, true: tokens.colors.primary }}
60
+ thumbColor="#fff"
61
+ />
62
+ </Pressable>
63
+
64
+ <ImageBackground
65
+ source={{ uri: 'https://images.unsplash.com/photo-1518621736915-f3b1c41bfd00?q=80&w=600&auto=format&fit=crop' }}
66
+ style={styles.decorativeCard}
67
+ imageStyle={{ borderRadius: 24, opacity: 0.6 }}
68
+ >
69
+ <View style={styles.decorativeOverlay}>
70
+ <AtomicText type="bodySmall" color="onPrimary" style={styles.decorativeQuote}>
71
+ {`"Words are the keys to the heart."`}
72
+ </AtomicText>
73
+ </View>
74
+ </ImageBackground>
75
+ </View>
76
+ );
77
+ };
78
+
79
+ const styles = StyleSheet.create({
80
+ profileCard: {
81
+ flexDirection: "row",
82
+ alignItems: "center",
83
+ justifyContent: "space-between",
84
+ padding: 16,
85
+ borderRadius: 20,
86
+ marginBottom: 24,
87
+ },
88
+ profileLeft: {
89
+ flexDirection: "row",
90
+ alignItems: "center",
91
+ flex: 1,
92
+ },
93
+ profileIconCircle: {
94
+ width: 40,
95
+ height: 40,
96
+ borderRadius: 20,
97
+ alignItems: "center",
98
+ justifyContent: "center",
99
+ marginRight: 12,
100
+ },
101
+ decorativeCard: {
102
+ width: '100%',
103
+ height: 160,
104
+ justifyContent: 'flex-end',
105
+ marginBottom: 24,
106
+ },
107
+ decorativeOverlay: {
108
+ padding: 20,
109
+ alignItems: 'center',
110
+ },
111
+ decorativeQuote: {
112
+ fontStyle: 'italic',
113
+ textAlign: 'center',
114
+ fontWeight: 'bold',
115
+ },
116
+ });
@@ -0,0 +1,30 @@
1
+ /**
2
+ * StepVibe Component
3
+ */
4
+
5
+ import React from "react";
6
+ import { View } from "react-native";
7
+ import { MessageType, MessageTone } from "../../domain/types";
8
+ import { TypeSelector } from "./TypeSelector";
9
+ import { ToneSelector } from "./ToneSelector";
10
+
11
+ interface StepVibeProps {
12
+ selectedType: MessageType;
13
+ setSelectedType: (type: MessageType) => void;
14
+ selectedTone: MessageTone;
15
+ setSelectedTone: (tone: MessageTone) => void;
16
+ }
17
+
18
+ export const StepVibe: React.FC<StepVibeProps> = ({
19
+ selectedType,
20
+ setSelectedType,
21
+ selectedTone,
22
+ setSelectedTone,
23
+ }) => {
24
+ return (
25
+ <View>
26
+ <TypeSelector selectedType={selectedType} onSelectType={setSelectedType} />
27
+ <ToneSelector selectedTone={selectedTone} onSelectTone={setSelectedTone} />
28
+ </View>
29
+ );
30
+ };
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Message Tone Selector Component
3
+ * Premium icon-based selector
4
+ */
5
+
6
+ import type { FC } from "react";
7
+ import { View, Pressable, StyleSheet, ScrollView } from "react-native";
8
+ import {
9
+ AtomicText,
10
+ AtomicIcon,
11
+ useAppDesignTokens,
12
+ type IconName,
13
+ } from "@umituz/react-native-design-system";
14
+ import { useLocalization } from "@umituz/react-native-localization";
15
+ import { MESSAGE_TONES } from "../../domain/constants";
16
+ import type { MessageTone } from "../../domain/types";
17
+
18
+ interface ToneSelectorProps {
19
+ selectedTone: MessageTone;
20
+ onSelectTone: (tone: MessageTone) => void;
21
+ }
22
+
23
+ export const ToneSelector: FC<ToneSelectorProps> = ({
24
+ selectedTone,
25
+ onSelectTone,
26
+ }) => {
27
+ const tokens = useAppDesignTokens();
28
+ const { t } = useLocalization();
29
+
30
+ return (
31
+ <View style={styles.container}>
32
+ <AtomicText type="labelLarge" color="textPrimary" style={styles.label}>
33
+ {t("loveMessage.tone")}
34
+ </AtomicText>
35
+ <ScrollView
36
+ horizontal
37
+ showsHorizontalScrollIndicator={false}
38
+ contentContainerStyle={styles.scrollContent}
39
+ >
40
+ {MESSAGE_TONES.map((config) => {
41
+ const isSelected = selectedTone === config.tone;
42
+ return (
43
+ <Pressable
44
+ key={config.tone}
45
+ onPress={() => onSelectTone(config.tone)}
46
+ style={[
47
+ styles.item,
48
+ {
49
+ backgroundColor: isSelected
50
+ ? tokens.colors.primary
51
+ : tokens.colors.surfaceSecondary,
52
+ borderColor: isSelected
53
+ ? tokens.colors.primary
54
+ : tokens.colors.borderLight,
55
+ },
56
+ ]}
57
+ >
58
+ <AtomicIcon
59
+ name={config.icon as IconName}
60
+ color={isSelected ? "onPrimary" : "secondary"}
61
+ size="sm"
62
+ style={styles.icon}
63
+ />
64
+ <AtomicText
65
+ type="labelSmall"
66
+ color={isSelected ? "onPrimary" : "textSecondary"}
67
+ >
68
+ {t(config.labelKey)}
69
+ </AtomicText>
70
+ </Pressable>
71
+ );
72
+ })}
73
+ </ScrollView>
74
+ </View>
75
+ );
76
+ };
77
+
78
+ const styles = StyleSheet.create({
79
+ container: {
80
+ marginBottom: 24,
81
+ },
82
+ label: {
83
+ marginBottom: 12,
84
+ },
85
+ scrollContent: {
86
+ gap: 8,
87
+ paddingRight: 20,
88
+ },
89
+ item: {
90
+ flexDirection: "row",
91
+ alignItems: "center",
92
+ paddingVertical: 8,
93
+ paddingHorizontal: 16,
94
+ borderRadius: 20,
95
+ borderWidth: 1.5,
96
+ },
97
+ icon: {
98
+ marginRight: 6,
99
+ },
100
+ });
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Trending Section Component
3
+ */
4
+
5
+ import React from "react";
6
+ import { View, Pressable, ImageBackground, StyleSheet } from "react-native";
7
+ import {
8
+ AtomicText,
9
+ AtomicIcon,
10
+ useAppDesignTokens,
11
+ } from "@umituz/react-native-design-system";
12
+ import { useLocalization } from "@umituz/react-native-localization";
13
+
14
+ interface TrendingSectionProps {
15
+ onViewAll: () => void;
16
+ }
17
+
18
+ export const TrendingSection: React.FC<TrendingSectionProps> = ({ onViewAll }) => {
19
+ const tokens = useAppDesignTokens();
20
+ const { t } = useLocalization();
21
+
22
+ return (
23
+ <View>
24
+ <View style={styles.sectionHeader}>
25
+ <AtomicText type="headlineSmall" color="textPrimary" style={styles.sectionTitle}>
26
+ {t("loveMessage.explore.trending")}
27
+ </AtomicText>
28
+ <Pressable onPress={onViewAll}>
29
+ <AtomicText type="labelMedium" color="primary">{t("loveMessage.explore.viewAll")}</AtomicText>
30
+ </Pressable>
31
+ </View>
32
+
33
+ <View style={styles.trendingContainer}>
34
+ <Pressable
35
+ onPress={onViewAll}
36
+ style={[styles.trendingCard, { backgroundColor: tokens.colors.surface, borderColor: tokens.colors.borderLight }]}
37
+ >
38
+ <ImageBackground
39
+ source={{ uri: 'https://images.unsplash.com/photo-1516589174184-c68526614488?q=80&w=200&auto=format&fit=crop' }}
40
+ style={styles.trendingThumb}
41
+ imageStyle={{ borderRadius: 12 }}
42
+ />
43
+ <View style={styles.trendingContent}>
44
+ <AtomicText type="labelLarge" color="textPrimary">Deep Connection</AtomicText>
45
+ <AtomicText type="bodySmall" color="textTertiary" numberOfLines={1}>
46
+ Thoughtful AI generated notes for partners.
47
+ </AtomicText>
48
+ <View style={styles.trendingTagRow}>
49
+ <View style={[styles.trendingTag, { backgroundColor: `${tokens.colors.primary}20` }]}>
50
+ <AtomicText type="bodySmall" color="primary" style={styles.tagText}>5K SENT</AtomicText>
51
+ </View>
52
+ </View>
53
+ </View>
54
+ <AtomicIcon name="chevron-forward" color="textTertiary" size="sm" />
55
+ </Pressable>
56
+
57
+ <Pressable
58
+ onPress={onViewAll}
59
+ style={[styles.trendingCard, { backgroundColor: tokens.colors.surface, borderColor: tokens.colors.borderLight }]}
60
+ >
61
+ <ImageBackground
62
+ source={{ uri: 'https://images.unsplash.com/photo-1529333166437-7750a6dd5a70?q=80&w=200&auto=format&fit=crop' }}
63
+ style={styles.trendingThumb}
64
+ imageStyle={{ borderRadius: 12 }}
65
+ />
66
+ <View style={styles.trendingContent}>
67
+ <AtomicText type="labelLarge" color="textPrimary">Short & Sweet</AtomicText>
68
+ <AtomicText type="bodySmall" color="textTertiary" numberOfLines={1}>
69
+ Quick texts to make their heart skip a beat.
70
+ </AtomicText>
71
+ <View style={styles.trendingTagRow}>
72
+ <View style={[styles.trendingTag, { backgroundColor: `${tokens.colors.primary}20` }]}>
73
+ <AtomicText type="bodySmall" color="primary" style={styles.tagText}>12K SENT</AtomicText>
74
+ </View>
75
+ </View>
76
+ </View>
77
+ <AtomicIcon name="chevron-forward" color="textTertiary" size="sm" />
78
+ </Pressable>
79
+ </View>
80
+ </View>
81
+ );
82
+ };
83
+
84
+ const styles = StyleSheet.create({
85
+ sectionHeader: {
86
+ flexDirection: 'row',
87
+ justifyContent: 'space-between',
88
+ alignItems: 'center',
89
+ paddingHorizontal: 20,
90
+ marginBottom: 16,
91
+ },
92
+ sectionTitle: {
93
+ fontWeight: 'bold',
94
+ },
95
+ trendingContainer: {
96
+ paddingHorizontal: 16,
97
+ gap: 12,
98
+ },
99
+ trendingCard: {
100
+ flexDirection: 'row',
101
+ alignItems: 'center',
102
+ padding: 12,
103
+ borderRadius: 20,
104
+ borderWidth: 1,
105
+ },
106
+ trendingThumb: {
107
+ width: 64,
108
+ height: 64,
109
+ borderRadius: 12,
110
+ backgroundColor: '#333',
111
+ marginRight: 12,
112
+ },
113
+ trendingContent: {
114
+ flex: 1,
115
+ justifyContent: 'center',
116
+ },
117
+ trendingTagRow: {
118
+ flexDirection: 'row',
119
+ marginTop: 6,
120
+ },
121
+ trendingTag: {
122
+ paddingHorizontal: 8,
123
+ paddingVertical: 2,
124
+ borderRadius: 6,
125
+ },
126
+ tagText: {
127
+ fontSize: 9,
128
+ fontWeight: 'bold',
129
+ },
130
+ });
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Message Type Selector Component
3
+ * Premium icon-based selector
4
+ */
5
+
6
+ import type { FC } from "react";
7
+ import { View, Pressable, StyleSheet } from "react-native";
8
+ import {
9
+ AtomicText,
10
+ AtomicIcon,
11
+ useAppDesignTokens,
12
+ type IconName,
13
+ } from "@umituz/react-native-design-system";
14
+ import { useLocalization } from "@umituz/react-native-localization";
15
+ import { MESSAGE_TYPES } from "../../domain/constants";
16
+ import type { MessageType } from "../../domain/types";
17
+
18
+ interface TypeSelectorProps {
19
+ selectedType: MessageType;
20
+ onSelectType: (type: MessageType) => void;
21
+ }
22
+
23
+ export const TypeSelector: FC<TypeSelectorProps> = ({
24
+ selectedType,
25
+ onSelectType,
26
+ }) => {
27
+ const tokens = useAppDesignTokens();
28
+ const { t } = useLocalization();
29
+
30
+ return (
31
+ <View style={styles.container}>
32
+ <AtomicText type="labelLarge" color="textPrimary" style={styles.label}>
33
+ {t("loveMessage.messageType")}
34
+ </AtomicText>
35
+ <View style={styles.grid}>
36
+ {MESSAGE_TYPES.map((config) => {
37
+ const isSelected = selectedType === config.type;
38
+ return (
39
+ <Pressable
40
+ key={config.type}
41
+ onPress={() => onSelectType(config.type)}
42
+ style={[
43
+ styles.item,
44
+ {
45
+ backgroundColor: isSelected
46
+ ? tokens.colors.primary
47
+ : tokens.colors.surfaceSecondary,
48
+ borderColor: isSelected
49
+ ? tokens.colors.primary
50
+ : tokens.colors.borderLight,
51
+ },
52
+ ]}
53
+ >
54
+ <AtomicIcon
55
+ name={config.icon as IconName}
56
+ color={isSelected ? "onPrimary" : "secondary"}
57
+ size="md"
58
+ style={styles.icon}
59
+ />
60
+ <AtomicText
61
+ type="labelSmall"
62
+ color={isSelected ? "onPrimary" : "textSecondary"}
63
+ >
64
+ {t(config.labelKey)}
65
+ </AtomicText>
66
+ </Pressable>
67
+ );
68
+ })}
69
+ </View>
70
+ </View>
71
+ );
72
+ };
73
+
74
+ const styles = StyleSheet.create({
75
+ container: {
76
+ marginBottom: 24,
77
+ },
78
+ label: {
79
+ marginBottom: 12,
80
+ },
81
+ grid: {
82
+ flexDirection: "row",
83
+ flexWrap: "wrap",
84
+ gap: 10,
85
+ },
86
+ item: {
87
+ flexDirection: "row",
88
+ alignItems: "center",
89
+ paddingVertical: 10,
90
+ paddingHorizontal: 12,
91
+ borderRadius: 12,
92
+ borderWidth: 1.5,
93
+ minWidth: "47%",
94
+ flex: 1,
95
+ },
96
+ icon: {
97
+ marginRight: 8,
98
+ },
99
+ });
@@ -0,0 +1,114 @@
1
+ /**
2
+ * useLoveMessageGenerator Hook
3
+ * Business logic for Love Message Generation
4
+ */
5
+
6
+ import { useState, useCallback, useEffect } from "react";
7
+ import { useAppIsFocused } from "@umituz/react-native-design-system";
8
+ import { MessageType, MessageTone } from "../../domain/types";
9
+ import { generateLoveMessage } from "../../infrastructure/services/LoveMessageService";
10
+ import { PartnerProfileRepository } from "../../infrastructure/persistence/PartnerProfileRepository";
11
+
12
+ export enum GeneratorStep {
13
+ PARTNER = 0,
14
+ VIBE = 1,
15
+ DETAILS = 2,
16
+ RESULT = 3
17
+ }
18
+
19
+ export const useLoveMessageGenerator = (config: {
20
+ onBack: () => void;
21
+ initialType?: MessageType;
22
+ onSuccess?: () => void;
23
+ onError?: () => void;
24
+ }) => {
25
+ const isFocused = useAppIsFocused();
26
+
27
+ const [currentStep, setCurrentStep] = useState<GeneratorStep>(GeneratorStep.PARTNER);
28
+ const [partnerName, setPartnerName] = useState("");
29
+ const [selectedType, setSelectedType] = useState(config.initialType || MessageType.ROMANTIC);
30
+ const [selectedTone, setSelectedTone] = useState(MessageTone.ROMANTIC);
31
+ const [details, setDetails] = useState("");
32
+ const [useProfile, setUseProfile] = useState(true);
33
+ const [hasProfile, setHasProfile] = useState(false);
34
+ const [generatedMessage, setGeneratedMessage] = useState("");
35
+ const [isGenerating, setIsGenerating] = useState(false);
36
+
37
+ useEffect(() => {
38
+ if (isFocused) {
39
+ const checkProfile = async () => {
40
+ const profile = await PartnerProfileRepository.getProfile();
41
+ if (profile) {
42
+ setHasProfile(true);
43
+ if (!partnerName) setPartnerName(profile.name);
44
+ } else {
45
+ setHasProfile(false);
46
+ }
47
+ };
48
+ checkProfile();
49
+ }
50
+ }, [isFocused, partnerName]);
51
+
52
+ const handleNext = useCallback(() => {
53
+ if (currentStep < GeneratorStep.DETAILS) {
54
+ setCurrentStep(currentStep + 1);
55
+ }
56
+ }, [currentStep]);
57
+
58
+ const handleBack = useCallback(() => {
59
+ if (currentStep > 0) {
60
+ setCurrentStep(currentStep - 1);
61
+ } else {
62
+ config.onBack();
63
+ }
64
+ }, [currentStep, config]);
65
+
66
+ const handleGenerate = useCallback(async () => {
67
+ if (!partnerName.trim() || isGenerating) return;
68
+
69
+ try {
70
+ setIsGenerating(true);
71
+ const message = await generateLoveMessage({
72
+ partnerName,
73
+ type: selectedType,
74
+ tone: selectedTone,
75
+ details,
76
+ usePartnerProfile: useProfile,
77
+ });
78
+
79
+ setGeneratedMessage(message);
80
+ setCurrentStep(GeneratorStep.RESULT);
81
+ config.onSuccess?.();
82
+ } catch {
83
+ config.onError?.();
84
+ } finally {
85
+ setIsGenerating(false);
86
+ }
87
+ }, [partnerName, selectedType, selectedTone, details, useProfile, isGenerating, config]);
88
+
89
+ const startOver = useCallback(() => {
90
+ setCurrentStep(GeneratorStep.PARTNER);
91
+ setGeneratedMessage("");
92
+ }, []);
93
+
94
+ return {
95
+ currentStep,
96
+ partnerName,
97
+ setPartnerName,
98
+ selectedType,
99
+ setSelectedType,
100
+ selectedTone,
101
+ setSelectedTone,
102
+ details,
103
+ setDetails,
104
+ useProfile,
105
+ setUseProfile,
106
+ hasProfile,
107
+ generatedMessage,
108
+ isGenerating,
109
+ handleNext,
110
+ handleBack,
111
+ handleGenerate,
112
+ startOver,
113
+ };
114
+ };
@@ -0,0 +1,43 @@
1
+ /**
2
+ * usePartnerProfile Hook
3
+ */
4
+
5
+ import { useState, useEffect, useCallback } from "react";
6
+ import { PartnerProfile } from "../../domain/types";
7
+ import { PartnerProfileRepository } from "../../infrastructure/persistence/PartnerProfileRepository";
8
+
9
+ export const usePartnerProfile = (onBack: () => void) => {
10
+ const [profile, setProfile] = useState<PartnerProfile>({
11
+ name: "",
12
+ nickname: "",
13
+ hobbies: "",
14
+ traits: "",
15
+ quirks: "",
16
+ });
17
+ const [isLoading, setIsLoading] = useState(true);
18
+
19
+ useEffect(() => {
20
+ const loadProfile = async () => {
21
+ const savedProfile = await PartnerProfileRepository.getProfile();
22
+ if (savedProfile) {
23
+ setProfile(savedProfile);
24
+ }
25
+ setIsLoading(false);
26
+ };
27
+ loadProfile();
28
+ }, []);
29
+
30
+ const handleSave = useCallback(async () => {
31
+ const success = await PartnerProfileRepository.saveProfile(profile);
32
+ if (success) {
33
+ onBack();
34
+ }
35
+ }, [profile, onBack]);
36
+
37
+ return {
38
+ profile,
39
+ setProfile,
40
+ isLoading,
41
+ handleSave,
42
+ };
43
+ };
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Love Message Stack
3
+ * Navigation for Love Message domain
4
+ */
5
+
6
+ import React from "react";
7
+ import {
8
+ StackNavigator,
9
+ type StackNavigatorConfig,
10
+ } from "@umituz/react-native-design-system";
11
+ import { LoveMessageExploreScreen } from "../screens/LoveMessageExploreScreen";
12
+ import { MessageListScreen } from "../screens/MessageListScreen";
13
+ import { LoveMessageGeneratorScreen } from "../screens/LoveMessageGeneratorScreen";
14
+ import { PartnerProfileScreen } from "../screens/PartnerProfileScreen";
15
+ import type { MessageType } from "../../domain/types";
16
+
17
+ export type LoveMessageStackParamList = {
18
+ LoveMessageExplore: undefined;
19
+ MessageList: { categoryId: string };
20
+ MessageGenerator: { initialType?: MessageType };
21
+ PartnerProfile: undefined;
22
+ };
23
+
24
+ const stackConfig: StackNavigatorConfig<LoveMessageStackParamList> = {
25
+ initialRouteName: "LoveMessageExplore",
26
+ screenOptions: {
27
+ headerShown: false,
28
+ },
29
+ screens: [
30
+ { name: "LoveMessageExplore", component: LoveMessageExploreScreen },
31
+ { name: "MessageList", component: MessageListScreen },
32
+ { name: "MessageGenerator", component: LoveMessageGeneratorScreen },
33
+ { name: "PartnerProfile", component: PartnerProfileScreen },
34
+ ],
35
+ };
36
+
37
+ export const LoveMessageStack: React.FC = () => {
38
+ return <StackNavigator config={stackConfig} />;
39
+ };