NTP-38623 | Merge Master
This commit is contained in:
25
App.tsx
25
App.tsx
@@ -20,36 +20,36 @@ import { Provider } from 'react-redux';
|
||||
import { PersistGate } from 'redux-persist/integration/react';
|
||||
import store, { persistor } from './src/store/store';
|
||||
|
||||
import { navigationRef } from '@utils/navigationUtlis';
|
||||
import FullScreenLoader from './RN-UI-LIB/src/components/FullScreenLoader';
|
||||
import { toastConfigs, ToastContainer } from './RN-UI-LIB/src/components/toast';
|
||||
import { navigationRef } from '@utils/navigationUtlis';
|
||||
|
||||
import { hydrateGlobalImageMap } from '@common/CachedImage';
|
||||
import { CLICKSTREAM_EVENT_NAMES, LocalStorageKeys } from '@common/Constants';
|
||||
import { sendDeviceDetailsToClickstream } from '@components/utlis/commonFunctions';
|
||||
import { linkingConf } from '@components/utlis/deeplinkingUtils';
|
||||
import { getBuildFlavour } from '@components/utlis/DeviceUtils';
|
||||
import { initSentry } from '@components/utlis/sentry';
|
||||
import { GLOBAL, setGlobalBuildFlavour } from '@constants/Global';
|
||||
import { AppStates } from '@interfaces/appStates';
|
||||
import analytics from '@react-native-firebase/analytics';
|
||||
import dayJs from 'dayjs';
|
||||
import { COLORS } from '@rn-ui-lib/colors';
|
||||
import { MILLISECONDS_IN_A_SECOND } from '@rn-ui-lib/utils/common';
|
||||
import { hydrateGlobalImageMap } from '@common/CachedImage';
|
||||
import { CLICKSTREAM_EVENT_NAMES, LocalStorageKeys } from '@common/Constants';
|
||||
import ErrorBoundary from './src/common/ErrorBoundary';
|
||||
import { type TDocumentObj } from '@screens/caseDetails/interface';
|
||||
import { addClickstreamEvent } from '@services/clickstreamEventService';
|
||||
import { setJsErrorHandler } from '@services/exception-handler.service';
|
||||
import syncSelfCallData from '@services/syncSelfCallData';
|
||||
import { getPermissionsToRequest } from '@utils/PermissionUtils';
|
||||
import dayJs from 'dayjs';
|
||||
import CodePushLoadingModal, { CodePushLoadingModalRef } from './CodePushModal';
|
||||
import ErrorBoundary from './src/common/ErrorBoundary';
|
||||
import ScreenshotBlocker from './src/components/utlis/ScreenshotBlocker';
|
||||
import { setItem } from './src/components/utlis/storageHelper';
|
||||
import { ENV } from './src/constants/config';
|
||||
import AuthRouter from './src/screens/auth/AuthRouter';
|
||||
import { type TDocumentObj } from '@screens/caseDetails/interface';
|
||||
import Permissions from './src/screens/permissions/Permissions';
|
||||
import { addClickstreamEvent } from '@services/clickstreamEventService';
|
||||
import { setJsErrorHandler } from '@services/exception-handler.service';
|
||||
import fetchUpdatedRemoteConfig from './src/services/firebaseFetchAndUpdate.service';
|
||||
import { StorageKeys } from './src/types/storageKeys';
|
||||
import CodePushLoadingModal, { CodePushLoadingModalRef } from './CodePushModal';
|
||||
import { initSentry } from '@components/utlis/sentry';
|
||||
import { AppStates } from '@interfaces/appStates';
|
||||
import syncSelfCallData from '@services/syncSelfCallData';
|
||||
|
||||
if (!__DEV__) {
|
||||
initSentry();
|
||||
@@ -97,6 +97,7 @@ function App() {
|
||||
|
||||
const askForPermissions = async () => {
|
||||
const permissionsToRequest = await getPermissionsToRequest();
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
PermissionsAndroid.requestMultiple(permissionsToRequest)
|
||||
.then(async (result) => {
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.avapp.callModule;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.telecom.TelecomManager;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -161,6 +162,28 @@ public class CallModule extends ReactContextBaseJavaModule {
|
||||
makeCall(activity, recipient, exotelNumber);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void getDefaultCallingApp(Promise promise) {
|
||||
try {
|
||||
TelecomManager telecomManager = (TelecomManager) reactContext.getSystemService(Context.TELECOM_SERVICE);
|
||||
if (telecomManager != null) {
|
||||
String defaultDialer = telecomManager.getDefaultDialerPackage();
|
||||
if (defaultDialer != null) {
|
||||
Log.d(TAG, "Default calling app: " + defaultDialer);
|
||||
promise.resolve(defaultDialer); // Send default dialer package name to JS side
|
||||
} else {
|
||||
Log.d(TAG, "No default calling app found.");
|
||||
promise.resolve(null); // No default dialer app
|
||||
}
|
||||
} else {
|
||||
promise.reject("ERROR", "TelecomManager is not available.");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error retrieving default calling app", e);
|
||||
promise.reject("ERROR", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void makeCall(Activity activity, String recipient, String exotelNumber) {
|
||||
Intent intent = new Intent(Intent.ACTION_CALL);
|
||||
intent.setData(Uri.parse("tel:" + pendingRecipient));
|
||||
@@ -176,6 +199,16 @@ public class CallModule extends ReactContextBaseJavaModule {
|
||||
Log.e(TAG, "No app found to handle the call.");
|
||||
}
|
||||
}
|
||||
@ReactMethod
|
||||
public void startDialerApp() {
|
||||
Intent launchIntent = reactContext.getPackageManager().getLaunchIntentForPackage(CALLING_APP_PACKAGE);
|
||||
if (launchIntent != null) {
|
||||
Log.d(TAG, "Call initiated successfully.");
|
||||
reactContext.startActivity(launchIntent);
|
||||
} else {
|
||||
Log.e(TAG, "No app found to handle the call.");
|
||||
}
|
||||
}
|
||||
|
||||
private void promptForInstall(Uri apkUri) {
|
||||
try {
|
||||
|
||||
@@ -1 +1 @@
|
||||
237
|
||||
238
|
||||
@@ -1 +1 @@
|
||||
2.18.0
|
||||
2.18.1
|
||||
@@ -1 +1 @@
|
||||
306
|
||||
307
|
||||
@@ -1 +1 @@
|
||||
100.2.2
|
||||
100.2.3
|
||||
@@ -8,4 +8,5 @@ export const APM_APP_NAME = 'cosmos-app';
|
||||
export const APM_BASE_URL = 'https://dev-longhorn-portal.np.navi-tech.in/apm-events';
|
||||
export const GOOGLE_SSO_CLIENT_ID =
|
||||
'60755663443-40k0fbrbbqv4ci4hrjlbrphab5fj387b.apps.googleusercontent.com';
|
||||
export const MS_CLARITY_PROJECT_ID = '';
|
||||
export const MS_CLARITY_PROJECT_ID = '';
|
||||
export const COSMOS_DIALER_PACKAGE_NAME = 'org.fossify.phone';
|
||||
|
||||
@@ -12,4 +12,5 @@ export const IS_DATA_SYNC_REQUIRED = true;
|
||||
export const DATA_SYNC_TIME_INTERVAL = 2 * MINUTES_IN_AN_HOUR * MILLISECONDS_IN_A_MINUTE; // 2hr
|
||||
export const GOOGLE_SSO_CLIENT_ID =
|
||||
'136591056725-ev8db4hrlud2m23n0o03or3cmmp3a3cq.apps.googleusercontent.com';
|
||||
export const MS_CLARITY_PROJECT_ID = 'n2nsbu7o78';
|
||||
export const MS_CLARITY_PROJECT_ID = 'n2nsbu7o78';
|
||||
export const COSMOS_DIALER_PACKAGE_NAME = 'org.fossify.phone';
|
||||
@@ -13,3 +13,5 @@ export const DATA_SYNC_TIME_INTERVAL = 2 * MINUTES_IN_AN_HOUR * MILLISECONDS_IN_
|
||||
export const GOOGLE_SSO_CLIENT_ID =
|
||||
'60755663443-40k0fbrbbqv4ci4hrjlbrphab5fj387b.apps.googleusercontent.com';
|
||||
export const MS_CLARITY_PROJECT_ID = '';
|
||||
export const COSMOS_DIALER_PACKAGE_NAME = 'org.fossify.phone';
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
import AppUpdate from './AppUpdate';
|
||||
import {
|
||||
IShouldDialerAppUpdate,
|
||||
setDefaultDialer,
|
||||
setShouldDialerAppUpdate,
|
||||
setShouldUpdate,
|
||||
} from '@reducers/appUpdateSlice';
|
||||
@@ -36,6 +37,9 @@ import PostOperativeHours from './PostOperativeHours';
|
||||
import { isFieldApp } from './utils';
|
||||
import { AppStates } from '@interfaces/appStates';
|
||||
import DialerAppUpdate from './DialerAppUpdate';
|
||||
import { getDefaultCallingApp } from '@components/utlis/callModuleUtils';
|
||||
import { COSMOS_DIALER_PACKAGE_NAME } from '@constants/config';
|
||||
import DefaultDialerScreen from './DefaultDialerScreen';
|
||||
|
||||
interface IBlockerScreen {
|
||||
children?: ReactNode;
|
||||
@@ -60,6 +64,9 @@ const BlockerScreen = (props: IBlockerScreen) => {
|
||||
);
|
||||
const shouldDialerAppUpdate =
|
||||
useAppSelector((state) => state.appUpdate.shouldDialerAppUpdate) || {};
|
||||
const isCosmosDialerDefault =
|
||||
useAppSelector((state) => state.appUpdate.isCosmosDialerDefault);
|
||||
|
||||
const withinOperativeHours = useAppSelector((state) => state.user?.withinOperativeHours);
|
||||
const isLoggedIn = useAppSelector((state: RootState) => state.user?.isLoggedIn);
|
||||
const isFieldAgent =
|
||||
@@ -155,6 +162,8 @@ const BlockerScreen = (props: IBlockerScreen) => {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
const handleDialerAppUpdate = async () => {
|
||||
const url = dialerAppState?.currentProdAPK;
|
||||
if (!shouldDialerAppUpdate.newApkCachedUrl) {
|
||||
@@ -235,6 +244,8 @@ const BlockerScreen = (props: IBlockerScreen) => {
|
||||
);
|
||||
deleteCachedApkFiles(true);
|
||||
}
|
||||
const defaultCallingApp = await getDefaultCallingApp()=== COSMOS_DIALER_PACKAGE_NAME;
|
||||
dispatch(setDefaultDialer(defaultCallingApp));
|
||||
},
|
||||
[dialerAppState, shouldDialerAppUpdate, isCosmosDiallerEnabled]
|
||||
);
|
||||
@@ -295,6 +306,10 @@ const BlockerScreen = (props: IBlockerScreen) => {
|
||||
return <DialerAppUpdate onAppUpdate={handleDialerAppUpdate} />;
|
||||
}
|
||||
|
||||
if (!isCosmosDialerDefault && isCosmosDiallerEnabled) {
|
||||
return <DefaultDialerScreen />
|
||||
}
|
||||
|
||||
if (shouldUpdate.switchToFallback) {
|
||||
const { heading, instructions } = BLOCKER_SCREEN_DATA.UNINSTALL_APP;
|
||||
return (
|
||||
|
||||
@@ -1484,7 +1484,10 @@ export const CLICKSTREAM_EVENT_NAMES = {
|
||||
name: 'FA_CALL_VIA_INHOUSE_DIALER_APP_FAILED',
|
||||
description: 'Call via inhouse dialer app failed',
|
||||
},
|
||||
|
||||
FA_COSMOS_DEFAULT_DIALER: {
|
||||
name: 'FA_COSMOS_DEFAULT_DIALER',
|
||||
description: 'Cosmos default dialer',
|
||||
}
|
||||
} as const;
|
||||
|
||||
export enum MimeType {
|
||||
|
||||
67
src/common/DefaultDialerScreen.tsx
Normal file
67
src/common/DefaultDialerScreen.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import DialerAppIcon from '@assets/icons/DialerAppIcon';
|
||||
import { startDialerApp } from '@components/utlis/callModuleUtils';
|
||||
import { COLORS } from '@rn-ui-lib/colors';
|
||||
import Button from '@rn-ui-lib/components/Button';
|
||||
import NavigationHeader from '@rn-ui-lib/components/NavigationHeader';
|
||||
import Text from '@rn-ui-lib/components/Text';
|
||||
import { GenericStyles } from '@rn-ui-lib/styles';
|
||||
import Layout from '@screens/layout/Layout';
|
||||
import React from 'react';
|
||||
import { Linking, Pressable, StyleSheet, View } from 'react-native';
|
||||
|
||||
|
||||
const DefaultDialerScreen: React.FC = () => {
|
||||
const openHelpForm = () => {
|
||||
Linking.openURL(
|
||||
'https://docs.google.com/forms/d/e/1FAIpQLSdKtdzB67-yyidd2Gh_52fYsLtL9QeuTmkUb6BZt4fAPJGyOg/viewform?usp=dialog'
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<View style={[GenericStyles.fill]}>
|
||||
<NavigationHeader
|
||||
title="Default Dialer"
|
||||
titleStyle={GenericStyles.pl16}
|
||||
rightActionable={
|
||||
<Pressable onPress={openHelpForm}>
|
||||
<Text style={styles.helpHeading}>Help</Text>
|
||||
</Pressable>
|
||||
}
|
||||
/>
|
||||
<View style={[GenericStyles.fill, GenericStyles.centerAligned, GenericStyles.ph24]}>
|
||||
<DialerAppIcon />
|
||||
<Text dark bold style={styles.heading}>
|
||||
Cosmos dialer
|
||||
</Text>
|
||||
<Text light style={GenericStyles.centerAlignedText}>
|
||||
Please make the cosmos dialer as default dialer to use cosmos.
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={[GenericStyles.elevation10, GenericStyles.p16, GenericStyles.whiteBackground]}>
|
||||
<Button
|
||||
title="Open Cosmos dialer"
|
||||
onPress={()=>startDialerApp()}
|
||||
style={GenericStyles.w100}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
heading: {
|
||||
fontWeight: 'bold',
|
||||
fontSize: 20,
|
||||
marginBottom: 16,
|
||||
},
|
||||
helpHeading: {
|
||||
color: COLORS.TEXT.WHITE,
|
||||
paddingVertical: 16,
|
||||
paddingHorizontal: 12,
|
||||
},
|
||||
});
|
||||
|
||||
export default DefaultDialerScreen;
|
||||
@@ -1,4 +1,4 @@
|
||||
import React,{ type ReactNode, useEffect, useRef } from 'react';
|
||||
import React, { type ReactNode, useEffect, useRef } from 'react';
|
||||
import { AppState, type AppStateStatus } from 'react-native';
|
||||
import dayJs from 'dayjs';
|
||||
import RNFS from 'react-native-fs';
|
||||
@@ -54,6 +54,7 @@ import useFirestoreUpdates from '@hooks/useFirestoreUpdates';
|
||||
import { handlePostOperativeHourActivity } from '@screens/caseDetails/utils/postOperationalHourActions';
|
||||
import { setPostOperationalHourRestrictions } from '@reducers/postOperationalHourRestrictionsSlice';
|
||||
import { isCallingApp } from './utils';
|
||||
import { getDefaultCallingApp } from '@components/utlis/callModuleUtils';
|
||||
|
||||
export enum FOREGROUND_TASKS {
|
||||
GEOLOCATION = 'GEOLOCATION',
|
||||
@@ -106,6 +107,13 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
|
||||
const resyncFirebase = useResyncFirebase();
|
||||
|
||||
const handleUpdateActivity = async () => {
|
||||
const isDialerEnabled = store?.getState()?.user?.featureFlags?.isCosmosDiallerEnabled;
|
||||
if (isDialerEnabled) {
|
||||
const defaultDialer = await getDefaultCallingApp();
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_COSMOS_DEFAULT_DIALER, {
|
||||
defaultDialer
|
||||
}, true);
|
||||
}
|
||||
const foregroundTimestamp = await getItem(StorageKeys.APP_FOREGROUND_TIMESTAMP);
|
||||
const backgroundTimestamp = await getItem(StorageKeys.APP_BACKGROUND_TIMESTAMP);
|
||||
const stateSetTimestamp = await getItem(StorageKeys.STATE_SET_TIMESTAMP);
|
||||
@@ -204,18 +212,18 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
|
||||
const allPermissionsGranted = permissionsToRequest?.length === 0;
|
||||
return allPermissionsGranted;
|
||||
};
|
||||
|
||||
|
||||
|
||||
const appVersion = getAppVersion();
|
||||
|
||||
const taskSyncToLonghorn = async () => {
|
||||
const allPermissionsGranted = await checkPermissions();
|
||||
const agentId= store.getState().user.user?.referenceId!;
|
||||
if(!agentId) {
|
||||
const allPermissionsGranted = await checkPermissions();
|
||||
const agentId = store.getState().user.user?.referenceId!;
|
||||
if (!agentId) {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_COSMOS_SYNC_TO_LONGHORN_ERROR);
|
||||
return;
|
||||
}
|
||||
await syncToLonghorn({allPermissionsGranted, agentId, appVersion, isSyncToastEnabled: false});
|
||||
await syncToLonghorn({ allPermissionsGranted, agentId, appVersion, isSyncToastEnabled: false });
|
||||
};
|
||||
|
||||
const tasks: IForegroundTask[] = [
|
||||
@@ -294,7 +302,7 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
|
||||
},
|
||||
];
|
||||
|
||||
if(isCallingApp()) {
|
||||
if (isCallingApp()) {
|
||||
tasks.push({
|
||||
taskId: FOREGROUND_TASKS.COSMOS_SYNC_WITH_LONGHORN,
|
||||
task: taskSyncToLonghorn,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
|
||||
import { Control, Controller, UseFormSetValue } from 'react-hook-form';
|
||||
import { useSelector } from 'react-redux';
|
||||
@@ -147,7 +147,7 @@ const RadioButton: React.FC<IRadioButton> = (props) => {
|
||||
key={option}
|
||||
id={option as string}
|
||||
value={options[option]?.text}
|
||||
containerStyle={[GenericStyles.whiteBackground]}
|
||||
containerStyle={[GenericStyles.whiteBackground, styles.mr6]}
|
||||
/>
|
||||
))}
|
||||
</RadioGroup>
|
||||
@@ -186,4 +186,10 @@ const RadioButton: React.FC<IRadioButton> = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
mr6: {
|
||||
marginRight: 6,
|
||||
},
|
||||
});
|
||||
|
||||
export default RadioButton;
|
||||
|
||||
@@ -8,3 +8,9 @@ export const launchCallIntent = (number: string, exotelnumber: string, filePatch
|
||||
export const isDialerAppValidApk = (filePath: string): Promise<boolean> => {
|
||||
return CallModule.isDialerAppValidApkFile(filePath);
|
||||
}
|
||||
|
||||
export const getDefaultCallingApp = (): Promise<string> => {
|
||||
return CallModule.getDefaultCallingApp();
|
||||
}
|
||||
|
||||
export const startDialerApp = (): void => CallModule.startDialerApp();
|
||||
@@ -13,3 +13,5 @@ export const DATA_SYNC_TIME_INTERVAL = 2 * MINUTES_IN_AN_HOUR * MILLISECONDS_IN_
|
||||
export const GOOGLE_SSO_CLIENT_ID =
|
||||
'60755663443-40k0fbrbbqv4ci4hrjlbrphab5fj387b.apps.googleusercontent.com';
|
||||
export const MS_CLARITY_PROJECT_ID = '';
|
||||
export const COSMOS_DIALER_PACKAGE_NAME = 'org.fossify.phone';
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ interface IAppUpdateSlice {
|
||||
switchToFallback: boolean;
|
||||
};
|
||||
shouldDialerAppUpdate: IShouldDialerAppUpdate;
|
||||
isCosmosDialerDefault: boolean;
|
||||
}
|
||||
|
||||
const initialState: IAppUpdateSlice = {
|
||||
@@ -24,6 +25,7 @@ const initialState: IAppUpdateSlice = {
|
||||
switchToFallback: false,
|
||||
isAppInstalled: false,
|
||||
},
|
||||
isCosmosDialerDefault: false,
|
||||
};
|
||||
|
||||
const AppUpdateSlice = createSlice({
|
||||
@@ -36,9 +38,12 @@ const AppUpdateSlice = createSlice({
|
||||
setShouldDialerAppUpdate: (state, action) => {
|
||||
state.shouldDialerAppUpdate = action.payload;
|
||||
},
|
||||
setDefaultDialer: (state, action) => {
|
||||
state.isCosmosDialerDefault = action.payload;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const { setShouldUpdate, setShouldDialerAppUpdate } = AppUpdateSlice.actions;
|
||||
export const { setShouldUpdate, setShouldDialerAppUpdate, setDefaultDialer } = AppUpdateSlice.actions;
|
||||
|
||||
export default AppUpdateSlice.reducer;
|
||||
|
||||
@@ -35,7 +35,7 @@ const SkipTracingAddressContainer: React.FC<IAddressContainer> = ({
|
||||
]}
|
||||
>
|
||||
<CustomLocationIcon />
|
||||
<Text style={[styles.textContainer, styles.noSkipTracingAddressText]}>No skip tracing found</Text>
|
||||
<Text style={[styles.textContainer, styles.noSkipTracingAddressText]}>No customer outreach address found</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export const PRIMARY_SOURCE_MAPPING = {
|
||||
[PrimarySourcesType.AVS_V1]: 'AVS_V1',
|
||||
[PrimarySourcesType.KARZA]: 'Karza',
|
||||
[PrimarySourcesType['Field App']]: 'COSMOS',
|
||||
[PrimarySourcesType.SKIP_TRACING]: 'Skip Tracing',
|
||||
[PrimarySourcesType.SKIP_TRACING]: 'Customer outreach',
|
||||
[PrimarySourcesType.ACCOUNT_AGGREGATOR]: 'Account Aggregator',
|
||||
};
|
||||
|
||||
@@ -30,7 +30,7 @@ export const ADDRESSES_TABS = [
|
||||
},
|
||||
{
|
||||
key: 'skipTracing',
|
||||
label: 'Skip Tracing',
|
||||
label: 'Customer Outreach',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -430,7 +430,7 @@ export interface INearbyCaseItemObj extends CaseDetail {
|
||||
|
||||
export enum GeolocationSourceMap {
|
||||
VKYC = 'VKYC',
|
||||
DATA_SUTRAM = 'Skip Tracing',
|
||||
DATA_SUTRAM = 'Customer outreach',
|
||||
}
|
||||
|
||||
export interface Tab {
|
||||
|
||||
Reference in New Issue
Block a user