Skip to content

Commit 34f1429

Browse files
authored
Merge pull request #699 from kaohenry9287/feat/692-wallet-creation-ui
feat(native)t: implement wallet creation component
2 parents 6bfa48a + f17bc82 commit 34f1429

File tree

5 files changed

+351
-21
lines changed

5 files changed

+351
-21
lines changed

apps/native/app/(tabs)/wallet/index.tsx

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import React from "react";
1+
import React, { useState } from "react";
22
import { StyleSheet, SafeAreaView, ScrollView } from "react-native";
33
import { useRouter } from "expo-router";
44
import { WalletList } from "../../../components/wallet/WalletList";
55
import { CreateWallet } from "../../../components/wallet/CreateWalletButton";
6+
import { WalletCreateDrawer } from "../../../components/wallet/WalletCreateDrawer";
7+
import { Colors } from "../../../constants/Colors";
68

79
const mockWallets = [
810
{ id: "1", name: "Wallet 2", balance: 1000, date: "May 22, 2024" },
@@ -11,9 +13,11 @@ const mockWallets = [
1113

1214
export default function Wallet() {
1315
const router = useRouter();
16+
const [isCreatingWallet, setIsCreatingWallet] = useState(false);
17+
const [showDiscardModal, setShowDiscardModal] = useState(false);
1418

1519
const handleWalletPress = (walletId: string) => {
16-
const selectedWallet = mockWallets.find(wallet => wallet.id === walletId);
20+
const selectedWallet = mockWallets.find((wallet) => wallet.id === walletId);
1721

1822
router.push({
1923
pathname: "/(tabs)/wallet/[walletId]",
@@ -27,18 +31,56 @@ export default function Wallet() {
2731
};
2832

2933
// Handle create wallet button press
30-
const handleCreateWallet = () => {
31-
console.log("Create Wallet pressed");
32-
// later this can call an API to actually create a wallet
34+
const handleCreateWalletToggle = () => {
35+
setIsCreatingWallet(true);
36+
};
37+
38+
const handleDrawerRequestClose = (isDirty: boolean) => {
39+
console.log("handleDrawerRequestClose called, isDirty:", isDirty);
40+
if (isDirty) {
41+
console.log("Showing discard modal");
42+
setShowDiscardModal(true);
43+
} else {
44+
console.log("Closing drawer directly");
45+
setIsCreatingWallet(false);
46+
}
47+
};
48+
49+
const handleDiscard = () => {
50+
setShowDiscardModal(false);
51+
setIsCreatingWallet(false);
52+
};
53+
54+
const handleKeep = () => {
55+
setShowDiscardModal(false);
56+
};
57+
58+
const handleFormSubmit = (data: { name: string; description: string }) => {
59+
console.log("Creating wallet with:", data);
60+
// TODO: Call API to create wallet
61+
setIsCreatingWallet(false);
3362
};
3463

3564
return (
3665
<SafeAreaView style={styles.safeArea}>
3766
<ScrollView
3867
style={styles.scrollView}
39-
showsVerticalScrollIndicator={false}>
68+
showsVerticalScrollIndicator={false}
69+
>
4070
{/* Create Wallet Button Component */}
41-
<CreateWallet onPress={handleCreateWallet} />
71+
<CreateWallet
72+
onPress={handleCreateWalletToggle}
73+
isActive={isCreatingWallet}
74+
/>
75+
<WalletCreateDrawer
76+
visible={isCreatingWallet}
77+
onRequestClose={handleDrawerRequestClose}
78+
onSubmit={handleFormSubmit}
79+
existingWalletNames={mockWallets.map((w) => w.name)}
80+
showDiscardPrompt={showDiscardModal}
81+
onDiscardConfirm={handleDiscard}
82+
onDiscardCancel={handleKeep}
83+
/>
4284

4385
<WalletList wallets={mockWallets} onWalletPress={handleWalletPress} />
4486
</ScrollView>
@@ -50,10 +92,10 @@ export default function Wallet() {
5092
const styles = StyleSheet.create({
5193
safeArea: {
5294
flex: 1,
53-
backgroundColor: "#f5f5f5",
95+
backgroundColor: Colors.lightGray,
5496
},
5597
scrollView: {
5698
flex: 1,
57-
backgroundColor: "#f5f5f5",
99+
backgroundColor: Colors.lightGray,
58100
},
59101
});

apps/native/components/ui/common/CustomTextInput.tsx

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ interface CustomTextInputProps {
1919
keyboardType?: "default" | "email-address" | "numeric" | "phone-pad";
2020
helperText?: string;
2121
error?: boolean;
22+
multiline?: boolean;
2223
}
2324

2425
const CustomTextInput: React.FC<CustomTextInputProps> = ({
@@ -30,10 +31,13 @@ const CustomTextInput: React.FC<CustomTextInputProps> = ({
3031
keyboardType = "default",
3132
helperText,
3233
error = false,
34+
multiline = false,
3335
}) => {
3436
const [isSecure, setIsSecure] = useState(secureTextEntry);
3537
const [isFocused, setIsFocused] = useState(false);
36-
const labelAnim = useRef(new Animated.Value(value ? 1 : 0)).current;
38+
const labelAnim = useRef(
39+
new Animated.Value(value || multiline ? 1 : 0),
40+
).current;
3741

3842
const handleFocus = () => {
3943
setIsFocused(true);
@@ -46,7 +50,8 @@ const CustomTextInput: React.FC<CustomTextInputProps> = ({
4650

4751
const handleBlur = () => {
4852
setIsFocused(false);
49-
if (!value) {
53+
// multiline do not need label animation
54+
if (!value && !multiline) {
5055
Animated.timing(labelAnim, {
5156
toValue: 0,
5257
duration: 150,
@@ -71,7 +76,8 @@ const CustomTextInput: React.FC<CustomTextInputProps> = ({
7176
}),
7277
color: error ? Colors.red : isFocused ? Colors.green : "#777",
7378
},
74-
]}>
79+
]}
80+
>
7581
{label}
7682
</Animated.Text>
7783

@@ -80,14 +86,28 @@ const CustomTextInput: React.FC<CustomTextInputProps> = ({
8086
styles.inputContainer,
8187
error && styles.inputError,
8288
isFocused && styles.inputFocused,
83-
]}>
89+
multiline && {
90+
height: "auto",
91+
alignItems: "flex-start",
92+
paddingBottom: 12,
93+
},
94+
]}
95+
>
8496
<TextInput
85-
placeholder={isFocused ? placeholder : ""}
97+
placeholder={isFocused || multiline ? placeholder : ""}
8698
secureTextEntry={isSecure}
8799
onChangeText={onChangeText}
88100
value={value}
89101
keyboardType={keyboardType}
90-
style={styles.input}
102+
multiline={multiline}
103+
style={[
104+
styles.input,
105+
multiline && {
106+
textAlignVertical: "bottom",
107+
paddingTop: 14,
108+
maxHeight: 70,
109+
},
110+
]}
91111
placeholderTextColor={Colors.darkGray}
92112
onFocus={handleFocus}
93113
onBlur={handleBlur}
@@ -101,8 +121,9 @@ const CustomTextInput: React.FC<CustomTextInputProps> = ({
101121
/>
102122
) : value.length > 0 && secureTextEntry ? (
103123
<TouchableOpacity
104-
onPress={() => setIsSecure(prev => !prev)}
105-
style={styles.icon}>
124+
onPress={() => setIsSecure((prev) => !prev)}
125+
style={styles.icon}
126+
>
106127
<MaterialCommunityIcons
107128
name={isSecure ? "eye-off" : "eye"}
108129
size={24}
@@ -112,7 +133,8 @@ const CustomTextInput: React.FC<CustomTextInputProps> = ({
112133
) : value.length > 0 ? (
113134
<TouchableOpacity
114135
onPress={() => onChangeText("")}
115-
style={styles.icon}>
136+
style={styles.icon}
137+
>
116138
<MaterialCommunityIcons
117139
name="close-circle"
118140
size={24}
@@ -152,6 +174,7 @@ const styles = StyleSheet.create({
152174
borderRadius: 4,
153175
backgroundColor: Colors.lightGray,
154176
paddingHorizontal: 14,
177+
paddingTop: 12,
155178
height: 60,
156179
borderBottomWidth: 2,
157180
borderBottomColor: "#BDBDBD",

apps/native/components/wallet/CreateWalletButton.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
import React from "react";
22
import { View, Text, StyleSheet, TouchableOpacity } from "react-native";
33
import { Ionicons } from "@expo/vector-icons";
4+
import { Colors } from "@/constants/Colors";
45

56
// Component for rendering the "Create Wallet" button with info icon
67
interface CreateWalletProps {
78
onPress: () => void; // callback when button is pressed
9+
isActive?: boolean;
810
}
911

10-
export const CreateWallet: React.FC<CreateWalletProps> = ({ onPress }) => {
12+
export const CreateWallet: React.FC<CreateWalletProps> = ({
13+
onPress,
14+
isActive = false,
15+
}) => {
1116
// Placeholder for info icon functionality (can show tooltip or modal later)
1217
const handleInfoPress = () => {
1318
console.log("Info icon pressed"); // placeholder for now
@@ -20,10 +25,12 @@ export const CreateWallet: React.FC<CreateWalletProps> = ({ onPress }) => {
2025
<Ionicons
2126
name="add"
2227
size={20}
23-
color="#666"
28+
color={isActive ? Colors.green : "#999"}
2429
style={{ marginRight: 6 }}
2530
/>
26-
<Text style={styles.text}>CREATE WALLET</Text>
31+
<Text style={[styles.text, isActive && styles.activeText]}>
32+
CREATE WALLET
33+
</Text>
2734
</TouchableOpacity>
2835

2936
{/* Small info circle beside the button */}
@@ -61,6 +68,9 @@ const styles = StyleSheet.create({
6168
fontWeight: "600",
6269
letterSpacing: 0.5,
6370
},
71+
activeText: {
72+
color: Colors.green,
73+
},
6474
infoCircle: {
6575
width: 26,
6676
height: 26,
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { Colors } from "@constants/Colors";
2+
import React from "react";
3+
import {
4+
Modal,
5+
View,
6+
Text,
7+
StyleSheet,
8+
TouchableOpacity,
9+
Pressable,
10+
} from "react-native";
11+
import { Ionicons } from "@expo/vector-icons";
12+
13+
interface Props {
14+
visible: boolean;
15+
onKeep: () => void;
16+
onDiscard: () => void;
17+
}
18+
19+
export const DiscardChangesModal: React.FC<Props> = ({
20+
visible,
21+
onKeep,
22+
onDiscard,
23+
}) => {
24+
return (
25+
<Modal transparent visible={visible} animationType="fade">
26+
<Pressable style={styles.overlay} onPress={onKeep} />
27+
<View style={styles.center}>
28+
<View style={styles.card}>
29+
<View style={styles.header}>
30+
<Text style={styles.title}>Discard changes?</Text>
31+
<TouchableOpacity onPress={onKeep}>
32+
<Ionicons name="close" size={24} color={Colors.darkGray} />
33+
</TouchableOpacity>
34+
</View>
35+
<Text style={styles.message}>
36+
Are you sure you want to discard the new wallet?
37+
</Text>
38+
39+
<View style={styles.actions}>
40+
<TouchableOpacity onPress={onKeep} style={styles.keepBtn}>
41+
<Text style={styles.keep}>KEEP CHANGES</Text>
42+
</TouchableOpacity>
43+
44+
<TouchableOpacity onPress={onDiscard} style={styles.discardBtn}>
45+
<Text style={styles.discard}>DISCARD</Text>
46+
</TouchableOpacity>
47+
</View>
48+
</View>
49+
</View>
50+
</Modal>
51+
);
52+
};
53+
54+
const styles = StyleSheet.create({
55+
overlay: {
56+
...StyleSheet.absoluteFillObject,
57+
backgroundColor: Colors.blackOverlay,
58+
},
59+
center: { flex: 1, justifyContent: "center", alignItems: "center" },
60+
card: {
61+
width: "85%",
62+
backgroundColor: Colors.white,
63+
borderRadius: 8,
64+
padding: 24,
65+
},
66+
header: {
67+
flexDirection: "row",
68+
justifyContent: "space-between",
69+
alignItems: "center",
70+
marginBottom: 16,
71+
},
72+
title: { fontSize: 20, fontWeight: "600", color: Colors.light.text },
73+
message: { fontSize: 16, color: Colors.darkGray, marginBottom: 28 },
74+
actions: { flexDirection: "row", justifyContent: "flex-end", gap: 8 },
75+
keepBtn: {
76+
paddingHorizontal: 12,
77+
paddingVertical: 8,
78+
},
79+
keep: { color: Colors.charcoal, fontWeight: "600" },
80+
discardBtn: {
81+
borderWidth: 1,
82+
borderColor: Colors.red,
83+
borderRadius: 4,
84+
paddingHorizontal: 12,
85+
paddingVertical: 8,
86+
},
87+
discard: { color: Colors.red, fontWeight: "600" },
88+
});

0 commit comments

Comments
 (0)