Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add otp example to bare rn app #1235

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions account-kit/rn-signer/example/src/screens/otp-auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,24 @@ import {

import Config from "react-native-config";
import { RNAlchemySigner } from "@account-kit/react-native-signer";
export default function MagicLinkAuthScreen() {

const signer = RNAlchemySigner({
client: { connection: { apiKey: Config.API_KEY! } },
});
Comment on lines +15 to +17
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this still work within the a specific screen since it always returns a singleton now?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, did this to silence a linting warning we were getting since the signer instance is also used in a useEffect


export default function OTPAuthScreen() {
const [email, setEmail] = useState<string>("");
const [user, setUser] = useState<User | null>(null);

const signer = RNAlchemySigner({
client: { connection: { apiKey: Config.API_KEY! } },
});

const [awaitingOtp, setAwaitingOtp] = useState<boolean>(false);

const [otpCode, setOtpCode] = useState<string>("");

const handleUserAuth = ({ otpCode }: { otpCode: string }) => {
const handleUserAuth = ({ code }: { code: string }) => {
setAwaitingOtp(false);
signer
.authenticate({
otpCode: otpCode,
otpCode: code,
type: "otp",
})
.then((res) => {
Expand Down Expand Up @@ -55,7 +56,7 @@ export default function MagicLinkAuthScreen() {
/>
<TouchableOpacity
style={styles.button}
onPress={() => handleUserAuth({ otpCode })}
onPress={() => handleUserAuth({ code: otpCode })}
>
<Text style={styles.buttonText}>Sign in</Text>
</TouchableOpacity>
Expand Down
1 change: 1 addition & 0 deletions examples/react-native-bare-example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@account-kit/react-native-signer": "^4.6.0",
"@account-kit/signer": "^4.6.0",
"@account-kit/smart-contracts": "^4.6.0",
"@react-navigation/bottom-tabs": "^7.2.0",
"@react-navigation/native": "7.0.3",
"@react-navigation/native-stack": "^7.1.14",
"crypto-browserify": "^3.12.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ app.get('/', (req, res) => {
const bundle = req.query.bundle;
const orgId = req.query.orgId;

res.redirect(`${appScheme}://home?bundle=${bundle}&orgId=${orgId}`);
res.redirect(`${appScheme}://magic-link?bundle=${bundle}&orgId=${orgId}`);
});

app.listen(port, () => {
Expand Down
30 changes: 22 additions & 8 deletions examples/react-native-bare-example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,36 @@
import React from 'react';

import {createStaticNavigation} from '@react-navigation/native';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import {SafeAreaProvider} from 'react-native-safe-area-context';

import HomeScreen from './screens/Home';
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
import {SafeAreaProvider} from 'react-native-safe-area-context';
import {Text} from 'react-native';
import MagicLinkScreen from './screens/magic-link';
import OTPAuthScreen from './screens/otp';

const linking = {
enabled: 'auto' as const /* Automatically generate paths for all screens */,
prefixes: ['rn-signer-demo://'],
};

const RootStack = createNativeStackNavigator({
initialRouteName: 'Home',
const RootStack = createBottomTabNavigator({
initialRouteName: 'MagicLinkScreen',
screens: {
Home: {
screen: HomeScreen,
linking: {path: 'home'},
MagicLinkScreen: {
screen: MagicLinkScreen,
linking: {path: 'magic-link'},
options: {
title: 'Magic Link',
tabBarIcon: () => <Text>🪄</Text>,
},
},
OTPAuthScreen: {
screen: OTPAuthScreen,
linking: {path: 'otp'},
options: {
title: 'OTP Auth',
tabBarIcon: () => <Text>🔑</Text>,
},
},
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
} from 'react-native';
import {API_KEY} from '@env';

const signer = new RNAlchemySigner({
const signer = RNAlchemySigner({
client: {connection: {apiKey: API_KEY!}},
});

Expand Down Expand Up @@ -101,7 +101,9 @@ export default function HomeScreen() {
<TouchableOpacity
style={styles.button}
onPress={() => {
signer.authenticate({email, type: 'email'}).catch(console.error);
signer
.authenticate({email, type: 'email', emailMode: 'magicLink'})
.catch(console.error);
}}>
<Text style={styles.buttonText}>Sign in</Text>
</TouchableOpacity>
Expand Down
173 changes: 173 additions & 0 deletions examples/react-native-bare-example/src/screens/otp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import React from 'react';
import type {User} from '@account-kit/signer';
import {useEffect, useState} from 'react';
import {
View,
Text,
TextInput,
StyleSheet,
TouchableOpacity,
} from 'react-native';

import {API_KEY} from '@env';
import {RNAlchemySigner} from '@account-kit/react-native-signer';
import {
createLightAccountAlchemyClient,
LightAccount,
} from '@account-kit/smart-contracts';
import {alchemy} from '@account-kit/infra';
import {sepolia} from '@account-kit/infra';

const signer = RNAlchemySigner({
client: {connection: {apiKey: API_KEY!}},
});

export default function OTPAuthScreen() {
const [email, setEmail] = useState<string>('');
const [user, setUser] = useState<User | null>(null);
const [account, setAccount] = useState<LightAccount | null>(null);
const [signerAddress, setSignerAddress] = useState<string | null>(null);

const [awaitingOtp, setAwaitingOtp] = useState<boolean>(false);

const [otpCode, setOtpCode] = useState<string>('');

const handleUserAuth = ({code}: {code: string}) => {
setAwaitingOtp(false);
signer
.authenticate({
otpCode: code,
type: 'otp',
})
.then(setUser)
.catch(console.error);
};

useEffect(() => {
// get the user if already logged in
signer.getAuthDetails().then(setUser);
}, []);

useEffect(() => {
if (user) {
createLightAccountAlchemyClient({
signer,
chain: sepolia,
transport: alchemy({apiKey: API_KEY!}),
}).then(client => {
setAccount(client.account);
});

signer.getAddress().then(address => {
setSignerAddress(address);
});
}
}, [user]);

return (
<View style={styles.container}>
{awaitingOtp ? (
<>
<TextInput
style={styles.textInput}
placeholderTextColor="gray"
placeholder="enter your OTP code"
onChangeText={setOtpCode}
value={otpCode}
/>
<TouchableOpacity
style={styles.button}
onPress={() => handleUserAuth({code: otpCode})}>
<Text style={styles.buttonText}>Sign in</Text>
</TouchableOpacity>
</>
) : !user ? (
<>
<TextInput
style={styles.textInput}
placeholderTextColor="gray"
placeholder="enter your email"
onChangeText={setEmail}
value={email}
/>
<TouchableOpacity
style={styles.button}
onPress={() => {
signer
.authenticate({
email,
type: 'email',
emailMode: 'otp',
})
.catch(console.error);
setAwaitingOtp(true);
}}>
<Text style={styles.buttonText}>Sign in</Text>
</TouchableOpacity>
</>
) : (
<>
<Text style={styles.userText}>
Currently logged in as: {user.email}
</Text>
<Text style={styles.userText}>OrgId: {user.orgId}</Text>
<Text style={styles.userText}>Address: {user.address}</Text>
<Text style={styles.userText}>
Light Account Address: {account?.address}
</Text>
<Text style={styles.userText}>Signer Address: {signerAddress}</Text>

<TouchableOpacity
style={styles.button}
onPress={() => signer.disconnect().then(() => setUser(null))}>
<Text style={styles.buttonText}>Sign out</Text>
</TouchableOpacity>
</>
)}
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#FFFFF',
paddingHorizontal: 20,
},
textInput: {
width: '100%',
height: 40,
borderColor: 'gray',
borderWidth: 1,
paddingHorizontal: 10,
backgroundColor: 'rgba(0,0,0,0.05)',
marginTop: 20,
marginBottom: 10,
},
box: {
width: 60,
height: 60,
marginVertical: 20,
},
button: {
width: 200,
padding: 10,
height: 50,
backgroundColor: 'rgb(147, 197, 253)',
borderRadius: 5,
alignItems: 'center',
justifyContent: 'center',
marginTop: 20,
},
buttonText: {
color: 'white',
fontWeight: 'bold',
textAlign: 'center',
},
userText: {
marginBottom: 10,
fontSize: 18,
},
});
Loading
Loading