diff --git a/app/mobile/ios/Podfile.lock b/app/mobile/ios/Podfile.lock index 90948826..71ae2de3 100644 --- a/app/mobile/ios/Podfile.lock +++ b/app/mobile/ios/Podfile.lock @@ -354,6 +354,8 @@ PODS: - React-logger (= 0.69.5) - React-perflogger (= 0.69.5) - ReactCommon/turbomodule/core (= 0.69.5) + - RNCClipboard (1.11.1): + - React-Core - RNGestureHandler (2.7.0): - React-Core - RNImageCropPicker (0.38.0): @@ -443,6 +445,7 @@ DEPENDENCIES: - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) - React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`) - 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`) - RNImageCropPicker (from `../node_modules/react-native-image-crop-picker`) - RNReanimated (from `../node_modules/react-native-reanimated`) @@ -543,6 +546,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/runtimeexecutor" ReactCommon: :path: "../node_modules/react-native/ReactCommon" + RNCClipboard: + :path: "../node_modules/@react-native-clipboard/clipboard" RNGestureHandler: :path: "../node_modules/react-native-gesture-handler" RNImageCropPicker: @@ -600,6 +605,7 @@ SPEC CHECKSUMS: React-RCTVibration: 42b34fde72e42446d9b08d2b9a3ddc2fa9ac6189 React-runtimeexecutor: c778439c3c430a5719d027d3c67423b390a221fe ReactCommon: ab1003b81be740fecd82509c370a45b1a7dda0c1 + RNCClipboard: 2834e1c4af68697089cdd455ee4a4cdd198fa7dd RNGestureHandler: 7673697e7c0e9391adefae4faa087442bc04af33 RNImageCropPicker: ffbba608264885c241cbf3a8f78eb7aeeb978241 RNReanimated: 7faa787e8d4493fbc95fab2ad331fa7625828cfa diff --git a/app/mobile/package.json b/app/mobile/package.json index 65422f68..aa768ecf 100644 --- a/app/mobile/package.json +++ b/app/mobile/package.json @@ -9,6 +9,7 @@ "web": "expo start --web" }, "dependencies": { + "@react-native-clipboard/clipboard": "^1.11.1", "@react-navigation/bottom-tabs": "^6.4.0", "@react-navigation/drawer": "^6.5.0", "@react-navigation/native": "^6.0.13", diff --git a/app/mobile/src/api/addAccountAccess.js b/app/mobile/src/api/addAccountAccess.js index d0494332..90584a1b 100644 --- a/app/mobile/src/api/addAccountAccess.js +++ b/app/mobile/src/api/addAccountAccess.js @@ -1,7 +1,7 @@ import { checkResponse, fetchWithTimeout } from './fetchUtil'; -export async function addAccountAccess(token, accountId) { - let access = await fetchWithTimeout(`/admin/accounts/${accountId}/auth?token=${token}`, { method: 'POST' }) +export async function addAccountAccess(server, token, accountId) { + let access = await fetchWithTimeout(`https://${server}/admin/accounts/${accountId}/auth?token=${token}`, { method: 'POST' }) checkResponse(access); return await access.json() } diff --git a/app/mobile/src/api/addAccountCreate.js b/app/mobile/src/api/addAccountCreate.js index 49e4015e..1642a0ab 100644 --- a/app/mobile/src/api/addAccountCreate.js +++ b/app/mobile/src/api/addAccountCreate.js @@ -1,7 +1,7 @@ import { checkResponse, fetchWithTimeout } from './fetchUtil'; -export async function addAccountCreate(token) { - let access = await fetchWithTimeout(`/admin/accounts?token=${token}`, { method: 'POST' }) +export async function addAccountCreate(server, token) { + let access = await fetchWithTimeout(`https://${server}/admin/accounts?token=${token}`, { method: 'POST' }) checkResponse(access); return await access.json() } diff --git a/app/mobile/src/api/removeAccount.js b/app/mobile/src/api/removeAccount.js index ff45a8b1..a98dfd85 100644 --- a/app/mobile/src/api/removeAccount.js +++ b/app/mobile/src/api/removeAccount.js @@ -1,7 +1,7 @@ import { checkResponse, fetchWithTimeout } from './fetchUtil'; -export async function removeAccount(token, accountId) { - let res = await fetchWithTimeout(`/admin/accounts/${accountId}?token=${token}`, { method: 'DELETE' }) +export async function removeAccount(server, token, accountId) { + let res = await fetchWithTimeout(`https://${server}/admin/accounts/${accountId}?token=${token}`, { method: 'DELETE' }) checkResponse(res); } diff --git a/app/mobile/src/api/setAccountStatus.js b/app/mobile/src/api/setAccountStatus.js index a76459fe..450726d1 100644 --- a/app/mobile/src/api/setAccountStatus.js +++ b/app/mobile/src/api/setAccountStatus.js @@ -1,7 +1,7 @@ import { checkResponse, fetchWithTimeout } from './fetchUtil'; -export async function setAccountStatus(token, accountId, disabled) { - let res = await fetchWithTimeout(`/admin/accounts/${accountId}/status?token=${token}`, { method: 'PUT', body: JSON.stringify(disabled) }) +export async function setAccountStatus(server, token, accountId, disabled) { + let res = await fetchWithTimeout(`https://${server}/admin/accounts/${accountId}/status?token=${token}`, { method: 'PUT', body: JSON.stringify(disabled) }) checkResponse(res); } diff --git a/app/mobile/src/dashboard/Dashboard.jsx b/app/mobile/src/dashboard/Dashboard.jsx index 084165a1..8dd99005 100644 --- a/app/mobile/src/dashboard/Dashboard.jsx +++ b/app/mobile/src/dashboard/Dashboard.jsx @@ -1,6 +1,8 @@ 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 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 { useLocation } from 'react-router-dom'; 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 ( Accounts - + - + - + - - + + @@ -57,11 +120,22 @@ export function Dashboard(props) { { item.handle } - actions.showAccessUser(item.accountId)}> - + accessUser(item.accountId)}> + - - + { item.disabled && ( + enableUser(item.accountId, true)}> + + + )} + { !item.disabled && ( + enableUser(item.accountId, false)}> + + + )} + removeUser(item.accountId)}> + + )} @@ -149,7 +223,16 @@ export function Dashboard(props) { > - Add User: + + Create Account: + + + Token: + Clipboard.setString(state.createToken)}> + { state.createToken } + + + Done @@ -168,7 +251,16 @@ export function Dashboard(props) { > - Access User: + + Access Account: + + + Token: + + { state.accessToken } + + + Done diff --git a/app/mobile/src/dashboard/Dashboard.styled.js b/app/mobile/src/dashboard/Dashboard.styled.js index a7aa5674..060b02f0 100644 --- a/app/mobile/src/dashboard/Dashboard.styled.js +++ b/app/mobile/src/dashboard/Dashboard.styled.js @@ -142,6 +142,12 @@ export const styles = StyleSheet.create({ modalBody: { padding: 8, }, + accessToken: { + padding: 16, + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + }, modalLabel: { paddingTop: 8, color: Colors.text, @@ -203,4 +209,18 @@ export const styles = StyleSheet.create({ flexDirection: 'row', 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, + }, }); diff --git a/app/mobile/src/dashboard/useDashboard.hook.js b/app/mobile/src/dashboard/useDashboard.hook.js index 7060ba03..bd6b88c1 100644 --- a/app/mobile/src/dashboard/useDashboard.hook.js +++ b/app/mobile/src/dashboard/useDashboard.hook.js @@ -7,6 +7,10 @@ import { getNodeConfig } from 'api/getNodeConfig'; import { setNodeConfig } from 'api/setNodeConfig'; import { getNodeAccounts } from 'api/getNodeAccounts'; 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) { @@ -23,6 +27,7 @@ export function useDashboard(config, server, token) { enableImage: true, enableAudio: true, enableVideo: true, + createToken: null, }); const navigate = useNavigate(); @@ -32,7 +37,7 @@ export function useDashboard(config, server, token) { } const setAccountItem = (item) => { - const { name, handle, imageSet, accountId } = item; + const { name, handle, imageSet, accountId, disabled } = item; let logo; if (imageSet) { @@ -41,7 +46,7 @@ export function useDashboard(config, server, token) { else { logo = 'avatar'; } - return { logo, name, handle, accountId }; + return { logo, name, handle, accountId, disabled }; } const refreshAccounts = async () => { @@ -71,14 +76,16 @@ export function useDashboard(config, server, token) { hideEditConfig: () => { updateState({ editConfig: false }); }, - showAddUser: () => { - updateState({ addUser: true }); + addUser: async () => { + const createToken = await addAccountCreate(server, token); + updateState({ addUser: true, createToken }); }, hideAddUser: () => { updateState({ addUser: false }); }, - showAccessUser: (accessId) => { - updateState({ accessUser: true, accountId: accessId }); + accessUser: async (accountId) => { + const accessToken = await addAccountAccess(server, token, accountId); + updateState({ accessUser: true, accessToken }); }, hideAccessUser: () => { updateState({ accessUser: false }); @@ -106,6 +113,14 @@ export function useDashboard(config, server, token) { const config = { accountStorage: Number(storage), domain, keyType, enableImage, enableAudio, enableVideo }; 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 }; diff --git a/app/mobile/yarn.lock b/app/mobile/yarn.lock index 31d6d586..e78c63ad 100644 --- a/app/mobile/yarn.lock +++ b/app/mobile/yarn.lock @@ -1511,6 +1511,11 @@ mkdirp "^1.0.4" 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": version "8.0.4" resolved "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-8.0.4.tgz"