mirror of
https://github.com/balzack/databag.git
synced 2025-02-11 19:19:16 +00:00
completed admin dashboard
This commit is contained in:
parent
62f4bda0f5
commit
532fe0b647
@ -354,6 +354,8 @@ PODS:
|
|||||||
- React-logger (= 0.69.5)
|
- React-logger (= 0.69.5)
|
||||||
- React-perflogger (= 0.69.5)
|
- React-perflogger (= 0.69.5)
|
||||||
- ReactCommon/turbomodule/core (= 0.69.5)
|
- ReactCommon/turbomodule/core (= 0.69.5)
|
||||||
|
- RNCClipboard (1.11.1):
|
||||||
|
- React-Core
|
||||||
- RNGestureHandler (2.7.0):
|
- RNGestureHandler (2.7.0):
|
||||||
- React-Core
|
- React-Core
|
||||||
- RNImageCropPicker (0.38.0):
|
- RNImageCropPicker (0.38.0):
|
||||||
@ -443,6 +445,7 @@ DEPENDENCIES:
|
|||||||
- React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`)
|
- React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`)
|
||||||
- React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`)
|
- React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`)
|
||||||
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
|
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
|
||||||
|
- "RNCClipboard (from `../node_modules/@react-native-clipboard/clipboard`)"
|
||||||
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
|
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
|
||||||
- RNImageCropPicker (from `../node_modules/react-native-image-crop-picker`)
|
- RNImageCropPicker (from `../node_modules/react-native-image-crop-picker`)
|
||||||
- RNReanimated (from `../node_modules/react-native-reanimated`)
|
- RNReanimated (from `../node_modules/react-native-reanimated`)
|
||||||
@ -543,6 +546,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: "../node_modules/react-native/ReactCommon/runtimeexecutor"
|
:path: "../node_modules/react-native/ReactCommon/runtimeexecutor"
|
||||||
ReactCommon:
|
ReactCommon:
|
||||||
:path: "../node_modules/react-native/ReactCommon"
|
:path: "../node_modules/react-native/ReactCommon"
|
||||||
|
RNCClipboard:
|
||||||
|
:path: "../node_modules/@react-native-clipboard/clipboard"
|
||||||
RNGestureHandler:
|
RNGestureHandler:
|
||||||
:path: "../node_modules/react-native-gesture-handler"
|
:path: "../node_modules/react-native-gesture-handler"
|
||||||
RNImageCropPicker:
|
RNImageCropPicker:
|
||||||
@ -600,6 +605,7 @@ SPEC CHECKSUMS:
|
|||||||
React-RCTVibration: 42b34fde72e42446d9b08d2b9a3ddc2fa9ac6189
|
React-RCTVibration: 42b34fde72e42446d9b08d2b9a3ddc2fa9ac6189
|
||||||
React-runtimeexecutor: c778439c3c430a5719d027d3c67423b390a221fe
|
React-runtimeexecutor: c778439c3c430a5719d027d3c67423b390a221fe
|
||||||
ReactCommon: ab1003b81be740fecd82509c370a45b1a7dda0c1
|
ReactCommon: ab1003b81be740fecd82509c370a45b1a7dda0c1
|
||||||
|
RNCClipboard: 2834e1c4af68697089cdd455ee4a4cdd198fa7dd
|
||||||
RNGestureHandler: 7673697e7c0e9391adefae4faa087442bc04af33
|
RNGestureHandler: 7673697e7c0e9391adefae4faa087442bc04af33
|
||||||
RNImageCropPicker: ffbba608264885c241cbf3a8f78eb7aeeb978241
|
RNImageCropPicker: ffbba608264885c241cbf3a8f78eb7aeeb978241
|
||||||
RNReanimated: 7faa787e8d4493fbc95fab2ad331fa7625828cfa
|
RNReanimated: 7faa787e8d4493fbc95fab2ad331fa7625828cfa
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
"web": "expo start --web"
|
"web": "expo start --web"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@react-native-clipboard/clipboard": "^1.11.1",
|
||||||
"@react-navigation/bottom-tabs": "^6.4.0",
|
"@react-navigation/bottom-tabs": "^6.4.0",
|
||||||
"@react-navigation/drawer": "^6.5.0",
|
"@react-navigation/drawer": "^6.5.0",
|
||||||
"@react-navigation/native": "^6.0.13",
|
"@react-navigation/native": "^6.0.13",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
||||||
|
|
||||||
export async function addAccountAccess(token, accountId) {
|
export async function addAccountAccess(server, token, accountId) {
|
||||||
let access = await fetchWithTimeout(`/admin/accounts/${accountId}/auth?token=${token}`, { method: 'POST' })
|
let access = await fetchWithTimeout(`https://${server}/admin/accounts/${accountId}/auth?token=${token}`, { method: 'POST' })
|
||||||
checkResponse(access);
|
checkResponse(access);
|
||||||
return await access.json()
|
return await access.json()
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
||||||
|
|
||||||
export async function addAccountCreate(token) {
|
export async function addAccountCreate(server, token) {
|
||||||
let access = await fetchWithTimeout(`/admin/accounts?token=${token}`, { method: 'POST' })
|
let access = await fetchWithTimeout(`https://${server}/admin/accounts?token=${token}`, { method: 'POST' })
|
||||||
checkResponse(access);
|
checkResponse(access);
|
||||||
return await access.json()
|
return await access.json()
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
||||||
|
|
||||||
export async function removeAccount(token, accountId) {
|
export async function removeAccount(server, token, accountId) {
|
||||||
let res = await fetchWithTimeout(`/admin/accounts/${accountId}?token=${token}`, { method: 'DELETE' })
|
let res = await fetchWithTimeout(`https://${server}/admin/accounts/${accountId}?token=${token}`, { method: 'DELETE' })
|
||||||
checkResponse(res);
|
checkResponse(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
||||||
|
|
||||||
export async function setAccountStatus(token, accountId, disabled) {
|
export async function setAccountStatus(server, token, accountId, disabled) {
|
||||||
let res = await fetchWithTimeout(`/admin/accounts/${accountId}/status?token=${token}`, { method: 'PUT', body: JSON.stringify(disabled) })
|
let res = await fetchWithTimeout(`https://${server}/admin/accounts/${accountId}/status?token=${token}`, { method: 'PUT', body: JSON.stringify(disabled) })
|
||||||
checkResponse(res);
|
checkResponse(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { TextInput, Alert, Switch, TouchableOpacity, View, Text, Modal, FlatList, KeyboardAvoidingView } from 'react-native';
|
import { TextInput, Alert, Switch, TouchableOpacity, View, Text, Modal, FlatList, KeyboardAvoidingView } from 'react-native';
|
||||||
|
import Clipboard from '@react-native-clipboard/clipboard';
|
||||||
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import Ionicons from '@expo/vector-icons/AntDesign';
|
import AntIcon from '@expo/vector-icons/AntDesign';
|
||||||
|
import MatIcon from '@expo/vector-icons/MaterialCommunityIcons';
|
||||||
import { styles } from './Dashboard.styled';
|
import { styles } from './Dashboard.styled';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { useDashboard } from './useDashboard.hook';
|
import { useDashboard } from './useDashboard.hook';
|
||||||
@ -26,22 +28,83 @@ export function Dashboard(props) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const addUser = async () => {
|
||||||
|
try {
|
||||||
|
await actions.addUser();
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
Alert.alert(
|
||||||
|
'Failed to Generate Access Token',
|
||||||
|
'Please try again.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const accessUser = async (accountId) => {
|
||||||
|
try {
|
||||||
|
await actions.accessUser(accountId);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
Alert.alert(
|
||||||
|
'Failed to Generate Access Token',
|
||||||
|
'Please try again.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeUser = (accountId) => {
|
||||||
|
Alert.alert(
|
||||||
|
"Deleting Account",
|
||||||
|
"Confirm?",
|
||||||
|
[
|
||||||
|
{ text: "Cancel", onPress: () => {}, },
|
||||||
|
{ text: "Delete", onPress: async() => {
|
||||||
|
try {
|
||||||
|
await actions.removeUser(accountId);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
Alert.alert(
|
||||||
|
"Failed to Delete Account",
|
||||||
|
"Please try again.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const enableUser = async (accountId, enabled) => {
|
||||||
|
try {
|
||||||
|
await actions.enableUser(accountId, enabled);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
Alert.alert(
|
||||||
|
'Failed to Update Account',
|
||||||
|
'Please try again.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container}>
|
<SafeAreaView style={styles.container}>
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
<Text style={styles.headerLabel}>Accounts</Text>
|
<Text style={styles.headerLabel}>Accounts</Text>
|
||||||
<TouchableOpacity onPress={actions.refresh}>
|
<TouchableOpacity onPress={actions.refresh}>
|
||||||
<Ionicons style={styles.icon} name={'reload1'} size={20} />
|
<AntIcon style={styles.icon} name={'reload1'} size={20} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<TouchableOpacity onPress={actions.showEditConfig}>
|
<TouchableOpacity onPress={actions.showEditConfig}>
|
||||||
<Ionicons style={styles.icon} name={'setting'} size={20} />
|
<AntIcon style={styles.icon} name={'setting'} size={20} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<TouchableOpacity onPress={actions.logout}>
|
<TouchableOpacity onPress={actions.logout}>
|
||||||
<Ionicons style={styles.icon} name={'logout'} size={20} />
|
<AntIcon style={styles.icon} name={'logout'} size={20} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<View style={styles.end}>
|
<View style={styles.end}>
|
||||||
<TouchableOpacity onPress={actions.showAddUser}>
|
<TouchableOpacity onPress={addUser}>
|
||||||
<Ionicons style={styles.icon} name={'adduser'} size={24} />
|
<AntIcon style={styles.icon} name={'adduser'} size={24} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@ -57,11 +120,22 @@ export function Dashboard(props) {
|
|||||||
<Text style={styles.handle}>{ item.handle }</Text>
|
<Text style={styles.handle}>{ item.handle }</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.control}>
|
<View style={styles.control}>
|
||||||
<TouchableOpacity activeOpacity={1} onPress={() => actions.showAccessUser(item.accountId)}>
|
<TouchableOpacity onPress={() => accessUser(item.accountId)}>
|
||||||
<Ionicons style={styles.icon} name={'unlock'} size={20} />
|
<AntIcon style={styles.icon} name={'unlock'} size={20} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<Ionicons style={styles.disable} name={'closecircleo'} size={20} />
|
{ item.disabled && (
|
||||||
<Ionicons style={styles.delete} name={'deleteuser'} size={20} />
|
<TouchableOpacity onPress={() => enableUser(item.accountId, true)}>
|
||||||
|
<AntIcon style={styles.disable} name={'playcircleo'} size={20} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
{ !item.disabled && (
|
||||||
|
<TouchableOpacity onPress={() => enableUser(item.accountId, false)}>
|
||||||
|
<MatIcon style={styles.disable} name={'block-helper'} size={20} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
<TouchableOpacity onPress={() => removeUser(item.accountId)}>
|
||||||
|
<AntIcon style={styles.delete} name={'deleteuser'} size={20} />
|
||||||
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
@ -149,7 +223,16 @@ export function Dashboard(props) {
|
|||||||
>
|
>
|
||||||
<KeyboardAvoidingView behavior="height" style={styles.modalBackground}>
|
<KeyboardAvoidingView behavior="height" style={styles.modalBackground}>
|
||||||
<View style={styles.modalContainer}>
|
<View style={styles.modalContainer}>
|
||||||
<Text style={styles.modalHeader}>Add User:</Text>
|
<View style={styles.modalHeader}>
|
||||||
|
<Text style={styles.modalHeaderText}>Create Account:</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.accessToken}>
|
||||||
|
<Text style={styles.tokenLabel}>Token:</Text>
|
||||||
|
<TouchableOpacity style={styles.copy} onPress={() => Clipboard.setString(state.createToken)}>
|
||||||
|
<Text style={styles.token}>{ state.createToken }</Text>
|
||||||
|
<AntIcon style={styles.icon} name={'copy1'} size={20} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
<View style={styles.modalControls}>
|
<View style={styles.modalControls}>
|
||||||
<TouchableOpacity style={styles.cancel} onPress={actions.hideAddUser}>
|
<TouchableOpacity style={styles.cancel} onPress={actions.hideAddUser}>
|
||||||
<Text>Done</Text>
|
<Text>Done</Text>
|
||||||
@ -168,7 +251,16 @@ export function Dashboard(props) {
|
|||||||
>
|
>
|
||||||
<KeyboardAvoidingView behavior="height" style={styles.modalBackground}>
|
<KeyboardAvoidingView behavior="height" style={styles.modalBackground}>
|
||||||
<View style={styles.modalContainer}>
|
<View style={styles.modalContainer}>
|
||||||
<Text style={styles.modalHeader}>Access User:</Text>
|
<View style={styles.modalHeader}>
|
||||||
|
<Text style={styles.modalHeaderText}>Access Account:</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.accessToken}>
|
||||||
|
<Text style={styles.tokenLabel}>Token:</Text>
|
||||||
|
<TouchableOpacity style={styles.copy}>
|
||||||
|
<Text style={styles.token}>{ state.accessToken }</Text>
|
||||||
|
<AntIcon style={styles.icon} name={'copy1'} size={20} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
<View style={styles.modalControls}>
|
<View style={styles.modalControls}>
|
||||||
<TouchableOpacity style={styles.cancel} onPress={actions.hideAccessUser}>
|
<TouchableOpacity style={styles.cancel} onPress={actions.hideAccessUser}>
|
||||||
<Text>Done</Text>
|
<Text>Done</Text>
|
||||||
|
@ -142,6 +142,12 @@ export const styles = StyleSheet.create({
|
|||||||
modalBody: {
|
modalBody: {
|
||||||
padding: 8,
|
padding: 8,
|
||||||
},
|
},
|
||||||
|
accessToken: {
|
||||||
|
padding: 16,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
modalLabel: {
|
modalLabel: {
|
||||||
paddingTop: 8,
|
paddingTop: 8,
|
||||||
color: Colors.text,
|
color: Colors.text,
|
||||||
@ -203,4 +209,18 @@ export const styles = StyleSheet.create({
|
|||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
marginTop: 8,
|
marginTop: 8,
|
||||||
},
|
},
|
||||||
|
tokenLabel: {
|
||||||
|
fontSize: 16,
|
||||||
|
color: Colors.text,
|
||||||
|
},
|
||||||
|
copy: {
|
||||||
|
marginLeft: 8,
|
||||||
|
alignItems: 'center',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
padding: 8,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: 4,
|
||||||
|
borderColor: Colors.divider,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
@ -7,6 +7,10 @@ import { getNodeConfig } from 'api/getNodeConfig';
|
|||||||
import { setNodeConfig } from 'api/setNodeConfig';
|
import { setNodeConfig } from 'api/setNodeConfig';
|
||||||
import { getNodeAccounts } from 'api/getNodeAccounts';
|
import { getNodeAccounts } from 'api/getNodeAccounts';
|
||||||
import { getAccountImageUrl } from 'api/getAccountImageUrl';
|
import { getAccountImageUrl } from 'api/getAccountImageUrl';
|
||||||
|
import { removeAccount } from 'api/removeAccount';
|
||||||
|
import { addAccountCreate } from 'api/addAccountCreate';
|
||||||
|
import { setAccountStatus } from 'api/setAccountStatus';
|
||||||
|
import { addAccountAccess } from 'api/addAccountAccess';
|
||||||
|
|
||||||
export function useDashboard(config, server, token) {
|
export function useDashboard(config, server, token) {
|
||||||
|
|
||||||
@ -23,6 +27,7 @@ export function useDashboard(config, server, token) {
|
|||||||
enableImage: true,
|
enableImage: true,
|
||||||
enableAudio: true,
|
enableAudio: true,
|
||||||
enableVideo: true,
|
enableVideo: true,
|
||||||
|
createToken: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -32,7 +37,7 @@ export function useDashboard(config, server, token) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const setAccountItem = (item) => {
|
const setAccountItem = (item) => {
|
||||||
const { name, handle, imageSet, accountId } = item;
|
const { name, handle, imageSet, accountId, disabled } = item;
|
||||||
|
|
||||||
let logo;
|
let logo;
|
||||||
if (imageSet) {
|
if (imageSet) {
|
||||||
@ -41,7 +46,7 @@ export function useDashboard(config, server, token) {
|
|||||||
else {
|
else {
|
||||||
logo = 'avatar';
|
logo = 'avatar';
|
||||||
}
|
}
|
||||||
return { logo, name, handle, accountId };
|
return { logo, name, handle, accountId, disabled };
|
||||||
}
|
}
|
||||||
|
|
||||||
const refreshAccounts = async () => {
|
const refreshAccounts = async () => {
|
||||||
@ -71,14 +76,16 @@ export function useDashboard(config, server, token) {
|
|||||||
hideEditConfig: () => {
|
hideEditConfig: () => {
|
||||||
updateState({ editConfig: false });
|
updateState({ editConfig: false });
|
||||||
},
|
},
|
||||||
showAddUser: () => {
|
addUser: async () => {
|
||||||
updateState({ addUser: true });
|
const createToken = await addAccountCreate(server, token);
|
||||||
|
updateState({ addUser: true, createToken });
|
||||||
},
|
},
|
||||||
hideAddUser: () => {
|
hideAddUser: () => {
|
||||||
updateState({ addUser: false });
|
updateState({ addUser: false });
|
||||||
},
|
},
|
||||||
showAccessUser: (accessId) => {
|
accessUser: async (accountId) => {
|
||||||
updateState({ accessUser: true, accountId: accessId });
|
const accessToken = await addAccountAccess(server, token, accountId);
|
||||||
|
updateState({ accessUser: true, accessToken });
|
||||||
},
|
},
|
||||||
hideAccessUser: () => {
|
hideAccessUser: () => {
|
||||||
updateState({ accessUser: false });
|
updateState({ accessUser: false });
|
||||||
@ -106,6 +113,14 @@ export function useDashboard(config, server, token) {
|
|||||||
const config = { accountStorage: Number(storage), domain, keyType, enableImage, enableAudio, enableVideo };
|
const config = { accountStorage: Number(storage), domain, keyType, enableImage, enableAudio, enableVideo };
|
||||||
await setNodeConfig(server, token, config);
|
await setNodeConfig(server, token, config);
|
||||||
},
|
},
|
||||||
|
enableUser: async (accountId, enabled) => {
|
||||||
|
await setAccountStatus(server, token, accountId, !enabled);
|
||||||
|
await refreshAccounts();
|
||||||
|
},
|
||||||
|
removeUser: async (accountId) => {
|
||||||
|
await removeAccount(server, token, accountId);
|
||||||
|
await refreshAccounts();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return { state, actions };
|
return { state, actions };
|
||||||
|
@ -1511,6 +1511,11 @@
|
|||||||
mkdirp "^1.0.4"
|
mkdirp "^1.0.4"
|
||||||
rimraf "^3.0.2"
|
rimraf "^3.0.2"
|
||||||
|
|
||||||
|
"@react-native-clipboard/clipboard@^1.11.1":
|
||||||
|
version "1.11.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@react-native-clipboard/clipboard/-/clipboard-1.11.1.tgz#d3a9e685ce2383b1e92b89a334896c5575cc103d"
|
||||||
|
integrity sha512-nvSIIHzybVWqYxcJE5hpT17ekxAAg383Ggzw5WrYHtkKX61N1AwaKSNmXs5xHV7pmKSOe/yWjtSwxIzfW51I5Q==
|
||||||
|
|
||||||
"@react-native-community/cli-clean@^8.0.4":
|
"@react-native-community/cli-clean@^8.0.4":
|
||||||
version "8.0.4"
|
version "8.0.4"
|
||||||
resolved "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-8.0.4.tgz"
|
resolved "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-8.0.4.tgz"
|
||||||
|
Loading…
Reference in New Issue
Block a user