feat: can scan Casdoor QR code to login (#27)

This commit is contained in:
IZUMI-Zu 2024-09-22 23:06:20 +08:00 committed by GitHub
parent e74a0ed1c7
commit 37a03e45cc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 256 additions and 145 deletions

35
App.js
View File

@ -17,7 +17,8 @@ import {NavigationContainer} from "@react-navigation/native";
import {PaperProvider} from "react-native-paper"; import {PaperProvider} from "react-native-paper";
import {SafeAreaView, Text} from "react-native"; import {SafeAreaView, Text} from "react-native";
import ContentLoader, {Circle, Rect} from "react-content-loader/native"; import ContentLoader, {Circle, Rect} from "react-content-loader/native";
import Toast from "react-native-toast-message"; import {ZoomInDownZoomOutUp, createNotifications} from "react-native-notificated";
import {GestureHandlerRootView} from "react-native-gesture-handler";
import {useMigrations} from "drizzle-orm/expo-sqlite/migrator"; import {useMigrations} from "drizzle-orm/expo-sqlite/migrator";
import Header from "./Header"; import Header from "./Header";
@ -27,6 +28,21 @@ import migrations from "./drizzle/migrations";
const App = () => { const App = () => {
const {success, error} = useMigrations(db, migrations); const {success, error} = useMigrations(db, migrations);
const {NotificationsProvider} = createNotifications({
duration: 800,
notificationPosition: "top",
animationConfig: ZoomInDownZoomOutUp,
isNotch: true,
notificationWidth: 350,
defaultStylesSettings: {
globalConfig: {
borderRadius: 15,
borderWidth: 2,
multiline: 3,
defaultIconType: "no-icon",
},
},
});
if (error) { if (error) {
return ( return (
@ -59,13 +75,16 @@ const App = () => {
} }
return ( return (
<NavigationContainer> <GestureHandlerRootView style={{flex: 1}}>
<PaperProvider> <NotificationsProvider>
<Header /> <NavigationContainer>
<NavigationBar /> <PaperProvider>
</PaperProvider> <Header />
<Toast /> <NavigationBar />
</NavigationContainer> </PaperProvider>
</NavigationContainer>
</NotificationsProvider>
</GestureHandlerRootView>
); );
}; };
export default App; export default App;

View File

@ -16,10 +16,9 @@ import React, {useEffect, useState} from "react";
import {WebView} from "react-native-webview"; import {WebView} from "react-native-webview";
import {Platform, SafeAreaView, StatusBar, StyleSheet, Text, TouchableOpacity} from "react-native"; import {Platform, SafeAreaView, StatusBar, StyleSheet, Text, TouchableOpacity} from "react-native";
import {Portal} from "react-native-paper"; import {Portal} from "react-native-paper";
import {useNotifications} from "react-native-notificated";
import SDK from "casdoor-react-native-sdk"; import SDK from "casdoor-react-native-sdk";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import Toast from "react-native-toast-message";
import EnterCasdoorSdkConfig from "./EnterCasdoorSdkConfig"; import EnterCasdoorSdkConfig from "./EnterCasdoorSdkConfig";
import useStore from "./useStorage"; import useStore from "./useStorage";
// import {LogBox} from "react-native"; // import {LogBox} from "react-native";
@ -31,6 +30,7 @@ const CasdoorLoginPage = ({onWebviewClose}) => {
onWebviewClose: PropTypes.func.isRequired, onWebviewClose: PropTypes.func.isRequired,
}; };
const {notify} = useNotifications();
const [casdoorLoginURL, setCasdoorLoginURL] = useState(""); const [casdoorLoginURL, setCasdoorLoginURL] = useState("");
const [showConfigPage, setShowConfigPage] = useState(true); const [showConfigPage, setShowConfigPage] = useState(true);
@ -40,6 +40,7 @@ const CasdoorLoginPage = ({onWebviewClose}) => {
redirectPath, redirectPath,
appName, appName,
organizationName, organizationName,
token,
getCasdoorConfig, getCasdoorConfig,
setUserInfo, setUserInfo,
setToken, setToken,
@ -65,6 +66,12 @@ const CasdoorLoginPage = ({onWebviewClose}) => {
} }
}, [serverUrl, clientId, redirectPath, appName, organizationName]); }, [serverUrl, clientId, redirectPath, appName, organizationName]);
useEffect(() => {
if (token) {
onWebviewClose();
}
}, [token]);
const onNavigationStateChange = async(navState) => { const onNavigationStateChange = async(navState) => {
const {redirectPath} = getCasdoorConfig(); const {redirectPath} = getCasdoorConfig();
if (navState.url.startsWith(redirectPath)) { if (navState.url.startsWith(redirectPath)) {
@ -77,11 +84,11 @@ const CasdoorLoginPage = ({onWebviewClose}) => {
}; };
const handleErrorResponse = (error) => { const handleErrorResponse = (error) => {
Toast.show({ notify("error", {
type: "error", params: {
text1: "Error", text1: "Error",
text2: error.description, text2: error.description,
autoHide: true, },
}); });
setShowConfigPage(true); setShowConfigPage(true);
}; };
@ -95,7 +102,7 @@ const CasdoorLoginPage = ({onWebviewClose}) => {
onWebviewClose={onWebviewClose} onWebviewClose={onWebviewClose}
/> />
)} )}
{!showConfigPage && casdoorLoginURL !== "" && ( {!showConfigPage && casdoorLoginURL !== "" && !token && (
<> <>
<TouchableOpacity <TouchableOpacity
style={styles.backButton} style={styles.backButton}

View File

@ -15,7 +15,7 @@
import React, {useCallback, useState} from "react"; import React, {useCallback, useState} from "react";
import {View} from "react-native"; import {View} from "react-native";
import {Button, IconButton, Menu, Text, TextInput} from "react-native-paper"; import {Button, IconButton, Menu, Text, TextInput} from "react-native-paper";
import Toast from "react-native-toast-message"; import {useNotifications} from "react-native-notificated";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
const EnterAccountDetails = ({onClose, onAdd, validateSecret}) => { const EnterAccountDetails = ({onClose, onAdd, validateSecret}) => {
@ -25,6 +25,8 @@ const EnterAccountDetails = ({onClose, onAdd, validateSecret}) => {
validateSecret: PropTypes.func.isRequired, validateSecret: PropTypes.func.isRequired,
}; };
const {notify} = useNotifications();
const [accountName, setAccountName] = useState(""); const [accountName, setAccountName] = useState("");
const [secretKey, setSecretKey] = useState(""); const [secretKey, setSecretKey] = useState("");
const [secretError, setSecretError] = useState(""); const [secretError, setSecretError] = useState("");
@ -51,21 +53,17 @@ const EnterAccountDetails = ({onClose, onAdd, validateSecret}) => {
} }
if (accountName.trim() === "" || secretKey.trim() === "") { if (accountName.trim() === "" || secretKey.trim() === "") {
Toast.show({ notify("error", {
type: "error", title: "Error",
text1: "Error", description: "Please fill in all the fields!",
text2: "Please fill in all the fields!",
autoHide: true,
}); });
return; return;
} }
if (secretError) { if (secretError) {
Toast.show({ notify("error", {
type: "error", title: "Error",
text1: "Invalid Secret Key", description: "Invalid Secret Key",
text2: "Please check your secret key and try again.",
autoHide: true,
}); });
return; return;
} }

View File

@ -15,7 +15,8 @@
import React, {useState} from "react"; import React, {useState} from "react";
import {ScrollView, Text, View} from "react-native"; import {ScrollView, Text, View} from "react-native";
import {Button, IconButton, Portal, TextInput} from "react-native-paper"; import {Button, IconButton, Portal, TextInput} from "react-native-paper";
import Toast from "react-native-toast-message"; import {useNotifications} from "react-native-notificated";
import SDK from "casdoor-react-native-sdk";
import DefaultCasdoorSdkConfig from "./DefaultCasdoorSdkConfig"; import DefaultCasdoorSdkConfig from "./DefaultCasdoorSdkConfig";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import ScanQRCodeForLogin from "./ScanLogin"; import ScanQRCodeForLogin from "./ScanLogin";
@ -38,8 +39,13 @@ const EnterCasdoorSdkConfig = ({onClose, onWebviewClose}) => {
setAppName, setAppName,
setOrganizationName, setOrganizationName,
setCasdoorConfig, setCasdoorConfig,
getCasdoorConfig,
setToken,
setUserInfo,
} = useStore(); } = useStore();
const {notify} = useNotifications();
const [showScanner, setShowScanner] = useState(false); const [showScanner, setShowScanner] = useState(false);
const closeConfigPage = () => { const closeConfigPage = () => {
@ -49,11 +55,11 @@ const EnterCasdoorSdkConfig = ({onClose, onWebviewClose}) => {
const handleSave = () => { const handleSave = () => {
if (!serverUrl || !clientId || !appName || !organizationName || !redirectPath) { if (!serverUrl || !clientId || !appName || !organizationName || !redirectPath) {
Toast.show({ notify("error", {
type: "error", params: {
text1: "Error", title: "Error",
text2: "Please fill in all the fields!", description: "Please fill in all the fields!",
autoHide: true, },
}); });
return; return;
} }
@ -66,11 +72,36 @@ const EnterCasdoorSdkConfig = ({onClose, onWebviewClose}) => {
const handleLogin = (loginInfo) => { const handleLogin = (loginInfo) => {
setServerUrl(loginInfo.serverUrl); setServerUrl(loginInfo.serverUrl);
setClientId(loginInfo.clientId); setClientId("");
setAppName(loginInfo.appName); setAppName("");
setOrganizationName(loginInfo.organizationName); setOrganizationName("");
setShowScanner(false);
onClose(); const sdk = new SDK(getCasdoorConfig());
try {
const accessToken = loginInfo.accessToken;
const userInfo = sdk.JwtDecode(accessToken);
setToken(accessToken);
setUserInfo(userInfo);
notify("success", {
params: {
title: "Success",
description: "Logged in successfully!",
},
});
setShowScanner(false);
onClose();
onWebviewClose();
} catch (error) {
notify("error", {
params: {
title: "Error in login",
description: error,
},
});
}
}; };
const handleUseDefault = () => { const handleUseDefault = () => {
@ -147,7 +178,7 @@ const EnterCasdoorSdkConfig = ({onClose, onWebviewClose}) => {
style={[styles.button, styles.outlinedButton]} style={[styles.button, styles.outlinedButton]}
labelStyle={styles.outlinedButtonLabel} labelStyle={styles.outlinedButtonLabel}
> >
Use Casdoor Demo Site Try with Casdoor Demo Site
</Button> </Button>
</View> </View>
</ScrollView> </ScrollView>

134
Header.js
View File

@ -15,7 +15,8 @@
import * as React from "react"; import * as React from "react";
import {Dimensions, StyleSheet, View} from "react-native"; import {Dimensions, StyleSheet, View} from "react-native";
import {Appbar, Avatar, Menu, Text, TouchableRipple} from "react-native-paper"; import {Appbar, Avatar, Menu, Text, TouchableRipple} from "react-native-paper";
import Toast from "react-native-toast-message"; import {useNotifications} from "react-native-notificated";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import CasdoorLoginPage, {CasdoorLogout} from "./CasdoorLoginPage"; import CasdoorLoginPage, {CasdoorLogout} from "./CasdoorLoginPage";
import useStore from "./useStorage"; import useStore from "./useStorage";
import {useAccountSync} from "./useAccountStore"; import {useAccountSync} from "./useAccountStore";
@ -24,9 +25,10 @@ const {width} = Dimensions.get("window");
const Header = () => { const Header = () => {
const {userInfo, clearAll} = useStore(); const {userInfo, clearAll} = useStore();
const {syncError, clearSyncError} = useAccountSync(); const {isSyncing, syncError, clearSyncError} = useAccountSync();
const [showLoginPage, setShowLoginPage] = React.useState(false); const [showLoginPage, setShowLoginPage] = React.useState(false);
const [menuVisible, setMenuVisible] = React.useState(false); const [menuVisible, setMenuVisible] = React.useState(false);
const {notify} = useNotifications();
const openMenu = () => setMenuVisible(true); const openMenu = () => setMenuVisible(true);
const closeMenu = () => setMenuVisible(false); const closeMenu = () => setMenuVisible(false);
@ -46,32 +48,40 @@ const Header = () => {
}; };
const handleSyncErrorPress = () => { const handleSyncErrorPress = () => {
Toast.show({ notify("error", {
type: "error", params: {
text1: "Sync Error", title: "Error",
text2: syncError || "An unknown error occurred during synchronization.", description: syncError || "An unknown error occurred during synchronization.",
autoHide: true, },
}); });
}; };
return ( return (
<Appbar.Header mode="center-aligned"> <Appbar.Header mode="small" style={styles.header}>
<View style={styles.leftContainer}>
{true && syncError && (
<Appbar.Action
icon="sync-alert"
color="#E53935"
size={24}
onPress={handleSyncErrorPress}
/>
)}
</View>
<Appbar.Content <Appbar.Content
title="Casdoor" title={
titleStyle={styles.titleText} <View style={styles.titleContainer}>
style={styles.titleContainer} <Text style={styles.titleTextCasdoor}>Casdoor</Text>
</View>
}
style={styles.titleWrapper}
/> />
<View style={styles.rightContainer}> <View style={styles.rightContainer}>
{userInfo !== null && (
<Icon
name={
isSyncing
? "cloud-sync-outline"
: syncError
? "cloud-off-outline"
: "cloud-check-outline"
}
size={22}
color={isSyncing ? "#FFC107" : syncError ? "#E53935" : "#4CAF50"}
style={styles.syncIcon}
onPress={(isSyncing || syncError === null) ? null : handleSyncErrorPress}
/>
)}
<Menu <Menu
visible={menuVisible} visible={menuVisible}
onDismiss={closeMenu} onDismiss={closeMenu}
@ -84,21 +94,19 @@ const Header = () => {
style={styles.buttonContainer} style={styles.buttonContainer}
> >
<View style={styles.buttonContent}> <View style={styles.buttonContent}>
<Text
style={[
styles.buttonText,
userInfo !== null && {marginRight: 8},
]}
>
{userInfo === null ? "Login" : userInfo.name}
</Text>
{userInfo !== null && ( {userInfo !== null && (
<Avatar.Image <Avatar.Image
size={24} size={28}
source={{uri: userInfo.avatar}} source={{uri: userInfo.avatar}}
style={styles.avatar} style={styles.avatar}
/> />
)} )}
<Text style={[
styles.buttonText,
userInfo === null && {marginLeft: 0},
]}>
{userInfo === null ? "Login" : userInfo.name}
</Text>
</View> </View>
</TouchableRipple> </TouchableRipple>
} }
@ -112,63 +120,63 @@ const Header = () => {
}; };
const styles = StyleSheet.create({ const styles = StyleSheet.create({
leftContainer: { header: {
position: "absolute", backgroundColor: "#F2F2F2",
left: 0, height: 56,
top: 0,
bottom: 0,
justifyContent: "center",
paddingLeft: width * 0.03,
}, },
rightContainer: { rightContainer: {
position: "absolute", flexDirection: "row",
right: 0, alignItems: "center",
top: 0, paddingRight: width * 0.04,
bottom: 0, },
justifyContent: "center", titleWrapper: {
paddingRight: width * 0.03, alignItems: "flex-start",
}, },
titleContainer: { titleContainer: {
position: "absolute", flexDirection: "row",
left: 0, alignItems: "baseline",
right: 0,
top: 0,
bottom: 0,
justifyContent: "center",
alignItems: "center",
}, },
titleText: { titleTextCasdoor: {
fontSize: Math.max(20, width * 0.045), fontSize: Math.max(24, width * 0.05),
fontWeight: "bold", fontWeight: "bold",
textAlign: "center", color: "#212121",
fontFamily: "Lato-Bold",
}, },
buttonContainer: { buttonContainer: {
borderRadius: 20, borderRadius: 24,
overflow: "hidden", overflow: "hidden",
borderWidth: 0.5,
borderColor: "#DDDDDD",
}, },
buttonContent: { buttonContent: {
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
paddingVertical: 8, paddingVertical: 6,
paddingHorizontal: 16, paddingHorizontal: 14,
}, },
buttonText: { buttonText: {
fontSize: Math.max(14, width * 0.035), fontSize: Math.max(14, width * 0.042),
fontWeight: "bold", fontWeight: "600",
marginLeft: 8,
color: "#424242",
fontFamily: "Roboto-Medium",
}, },
menuContent: { menuContent: {
backgroundColor: "#FFFFFF", backgroundColor: "#FAFAFA",
borderRadius: 8, borderRadius: 8,
elevation: 3, elevation: 2,
shadowColor: "#000000", shadowColor: "#000000",
shadowOffset: {width: 0, height: 2}, shadowOffset: {width: 0, height: 1},
shadowOpacity: 0.2, shadowOpacity: 0.1,
shadowRadius: 3, shadowRadius: 2,
}, },
avatar: { avatar: {
backgroundColor: "transparent", backgroundColor: "transparent",
}, },
syncIcon: {
marginRight: 12,
},
}); });
export default Header; export default Header;

View File

@ -19,7 +19,7 @@ import {GestureHandlerRootView, Swipeable} from "react-native-gesture-handler";
import {CountdownCircleTimer} from "react-native-countdown-circle-timer"; import {CountdownCircleTimer} from "react-native-countdown-circle-timer";
import {useNetInfo} from "@react-native-community/netinfo"; import {useNetInfo} from "@react-native-community/netinfo";
import {FlashList} from "@shopify/flash-list"; import {FlashList} from "@shopify/flash-list";
import Toast from "react-native-toast-message"; import {useNotifications} from "react-native-notificated";
import SearchBar from "./SearchBar"; import SearchBar from "./SearchBar";
import EnterAccountDetails from "./EnterAccountDetails"; import EnterAccountDetails from "./EnterAccountDetails";
@ -50,12 +50,12 @@ export default function HomePage() {
const {isConnected} = useNetInfo(); const {isConnected} = useNetInfo();
const [canSync, setCanSync] = useState(false); const [canSync, setCanSync] = useState(false);
const [key, setKey] = useState(0); const [key, setKey] = useState(0);
const swipeableRef = useRef(null); const swipeableRef = useRef(null);
const {userInfo, serverUrl, token} = useStore(); const {userInfo, serverUrl, token} = useStore();
const {startSync} = useAccountSync(); const {startSync} = useAccountSync();
const {accounts, refreshAccounts} = useAccountStore(); const {accounts, refreshAccounts} = useAccountStore();
const {setAccount, updateAccount, insertAccount, insertAccounts, deleteAccount} = useEditAccount(); const {setAccount, updateAccount, insertAccount, insertAccounts, deleteAccount} = useEditAccount();
const {notify} = useNotifications();
useEffect(() => { useEffect(() => {
refreshAccounts(); refreshAccounts();
@ -79,25 +79,25 @@ export default function HomePage() {
} }
}, REFRESH_INTERVAL); }, REFRESH_INTERVAL);
return () => clearInterval(timer); return () => clearInterval(timer);
}, [startSync, canSync]); }, [startSync, canSync, token]);
const onRefresh = async() => { const onRefresh = async() => {
setRefreshing(true); setRefreshing(true);
if (canSync) { if (canSync) {
const syncError = await startSync(userInfo, serverUrl, token); const syncError = await startSync(userInfo, serverUrl, token);
if (syncError) { if (syncError) {
Toast.show({ notify("error", {
type: "error", params: {
text1: "Sync error", title: "Sync error",
text2: syncError, description: syncError,
autoHide: true, },
}); });
} else { } else {
Toast.show({ notify("success", {
type: "success", params: {
text1: "Sync success", title: "Sync success",
text2: "All your accounts are up to date.", description: "All your accounts are up to date.",
autoHide: true, },
}); });
} }
} }
@ -151,11 +151,11 @@ export default function HomePage() {
const handleScanError = (error) => { const handleScanError = (error) => {
setShowScanner(false); setShowScanner(false);
Toast.show({ notify("error", {
type: "error", params: {
text1: "Scan error", title: "Error scanning QR code",
text2: error, description: error,
autoHide: true, },
}); });
}; };
@ -226,7 +226,8 @@ export default function HomePage() {
<List.Item <List.Item
style={{ style={{
height: 80, height: 80,
paddingHorizontal: 25, paddingVertical: 6,
paddingHorizontal: 16,
justifyContent: "center", justifyContent: "center",
}} }}
title={ title={

View File

@ -26,7 +26,7 @@ const ScanQRCodeForLogin = ({onClose, showScanner, onLogin}) => {
}; };
const isValidLoginQR = (data) => { const isValidLoginQR = (data) => {
return data.startsWith("casdoor-app://login/into?"); return data.startsWith("casdoor-app://login?");
}; };
const parseLoginQR = (data) => { const parseLoginQR = (data) => {
@ -34,10 +34,11 @@ const ScanQRCodeForLogin = ({onClose, showScanner, onLogin}) => {
const params = new URLSearchParams(url.search); const params = new URLSearchParams(url.search);
return { return {
// clientId: params.get("clientId"),
// appName: params.get("appName"),
// organizationName: params.get("organizationName"),
serverUrl: params.get("serverUrl"), serverUrl: params.get("serverUrl"),
clientId: params.get("clientId"), accessToken: params.get("accessToken"),
appName: params.get("appName"),
organizationName: params.get("organizationName"),
}; };
}; };

View File

@ -13,25 +13,47 @@
// limitations under the License. // limitations under the License.
import * as React from "react"; import * as React from "react";
import {View} from "react-native";
import {Searchbar} from "react-native-paper"; import {Searchbar} from "react-native-paper";
const SearchBar = ({onSearch}) => { const SearchBar = ({onSearch}) => {
const [searchQuery, setSearchQuery] = React.useState(""); const [searchQuery, setSearchQuery] = React.useState("");
const onChangeSearch = query => { const onChangeSearch = (query) => {
setSearchQuery(query); setSearchQuery(query);
onSearch(query); onSearch(query);
}; };
return ( return (
<Searchbar <View style={styles.container}>
placeholder="Search" <Searchbar
onChangeText={onChangeSearch} placeholder="Search"
value={searchQuery} onChangeText={onChangeSearch}
style={{height: 48, backgroundColor: "#E6DFF3"}} value={searchQuery}
inputStyle={{textAlignVertical: "center", justifyContent: "center", alignItems: "center", minHeight: 0}} style={styles.searchbar}
/> inputStyle={styles.inputStyle}
/>
</View>
); );
}; };
const styles = {
container: {
alignItems: "center",
paddingTop: 2,
},
searchbar: {
height: 56,
backgroundColor: "#E6DFF3",
borderRadius: 99,
width: "95%",
},
inputStyle: {
minHeight: 0,
textAlignVertical: "center",
justifyContent: "center",
alignItems: "center",
},
};
export default SearchBar; export default SearchBar;

13
api.js
View File

@ -32,6 +32,12 @@ export const getMfaAccounts = async(serverUrl, owner, name, token, timeoutMs = T
]); ]);
const res = await result.json(); const res = await result.json();
// Check the response status and message
if (res.status === "error") {
throw new Error(res.msg);
}
return {updatedTime: res.data.updatedTime, mfaAccounts: res.data.mfaAccounts}; return {updatedTime: res.data.updatedTime, mfaAccounts: res.data.mfaAccounts};
} catch (error) { } catch (error) {
if (error.name === "AbortError") { if (error.name === "AbortError") {
@ -58,6 +64,7 @@ export const updateMfaAccounts = async(serverUrl, owner, name, newMfaAccounts, t
]); ]);
const userData = await getUserResult.json(); const userData = await getUserResult.json();
userData.data.mfaAccounts = newMfaAccounts; userData.data.mfaAccounts = newMfaAccounts;
const updateResult = await Promise.race([ const updateResult = await Promise.race([
@ -74,6 +81,12 @@ export const updateMfaAccounts = async(serverUrl, owner, name, newMfaAccounts, t
]); ]);
const res = await updateResult.json(); const res = await updateResult.json();
// Check the response status and message
if (res.status === "error") {
throw new Error(res.msg);
}
return {status: res.status, data: res.data}; return {status: res.status, data: res.data};
} catch (error) { } catch (error) {
if (error.name === "AbortError") { if (error.name === "AbortError") {

View File

@ -2,6 +2,14 @@ module.exports = function(api) {
api.cache(true); api.cache(true);
return { return {
presets: ["babel-preset-expo"], presets: ["babel-preset-expo"],
plugins: [["inline-import", {"extensions": [".sql"]}]], plugins: [
[
"inline-import",
{
"extensions": [".sql"],
},
],
"react-native-reanimated/plugin",
],
}; };
}; };

23
package-lock.json generated
View File

@ -40,12 +40,12 @@
"react-native": "0.74.5", "react-native": "0.74.5",
"react-native-countdown-circle-timer": "^3.2.1", "react-native-countdown-circle-timer": "^3.2.1",
"react-native-gesture-handler": "~2.16.1", "react-native-gesture-handler": "~2.16.1",
"react-native-notificated": "^0.1.6",
"react-native-paper": "^5.10.3", "react-native-paper": "^5.10.3",
"react-native-reanimated": "~3.10.1", "react-native-reanimated": "~3.10.1",
"react-native-safe-area-context": "4.10.5", "react-native-safe-area-context": "4.10.5",
"react-native-screens": "3.31.1", "react-native-screens": "3.31.1",
"react-native-svg": "15.2.0", "react-native-svg": "15.2.0",
"react-native-toast-message": "^2.2.0",
"react-native-web": "~0.19.6", "react-native-web": "~0.19.6",
"react-native-webview": "13.8.6", "react-native-webview": "13.8.6",
"totp-generator": "^0.0.14", "totp-generator": "^0.0.14",
@ -15644,6 +15644,18 @@
"react-native": "*" "react-native": "*"
} }
}, },
"node_modules/react-native-notificated": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/react-native-notificated/-/react-native-notificated-0.1.6.tgz",
"integrity": "sha512-thv+uhQlDHzdKOL2QJcaDC/IT7F9NivdIPutbnr6YZ+MfINr5qeVOeEAuUs+4xIxADvP3MVGehyTGIYnAUU8Og==",
"license": "MIT",
"peerDependencies": {
"react": "*",
"react-native": "*",
"react-native-gesture-handler": "^2.9.0",
"react-native-reanimated": "^2.14.4 || ^3.0.0"
}
},
"node_modules/react-native-paper": { "node_modules/react-native-paper": {
"version": "5.10.6", "version": "5.10.6",
"resolved": "https://registry.npmjs.org/react-native-paper/-/react-native-paper-5.10.6.tgz", "resolved": "https://registry.npmjs.org/react-native-paper/-/react-native-paper-5.10.6.tgz",
@ -15807,15 +15819,6 @@
"url": "https://github.com/fb55/entities?sponsor=1" "url": "https://github.com/fb55/entities?sponsor=1"
} }
}, },
"node_modules/react-native-toast-message": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/react-native-toast-message/-/react-native-toast-message-2.2.0.tgz",
"integrity": "sha512-AFti8VzUk6JvyGAlLm9/BknTNDXrrhqnUk7ak/pM7uCTxDPveAu2ekszU0on6vnUPFnG04H/QfYE2IlETqeaWw==",
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native-vector-icons": { "node_modules/react-native-vector-icons": {
"version": "10.0.0", "version": "10.0.0",
"resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-10.0.0.tgz", "resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-10.0.0.tgz",

View File

@ -42,12 +42,12 @@
"react-native": "0.74.5", "react-native": "0.74.5",
"react-native-countdown-circle-timer": "^3.2.1", "react-native-countdown-circle-timer": "^3.2.1",
"react-native-gesture-handler": "~2.16.1", "react-native-gesture-handler": "~2.16.1",
"react-native-notificated": "^0.1.6",
"react-native-paper": "^5.10.3", "react-native-paper": "^5.10.3",
"react-native-reanimated": "~3.10.1", "react-native-reanimated": "~3.10.1",
"react-native-safe-area-context": "4.10.5", "react-native-safe-area-context": "4.10.5",
"react-native-screens": "3.31.1", "react-native-screens": "3.31.1",
"react-native-svg": "15.2.0", "react-native-svg": "15.2.0",
"react-native-toast-message": "^2.2.0",
"react-native-web": "~0.19.6", "react-native-web": "~0.19.6",
"react-native-webview": "13.8.6", "react-native-webview": "13.8.6",
"totp-generator": "^0.0.14", "totp-generator": "^0.0.14",