Skip to content

Commit

Permalink
Merge pull request #1988 from ever-co/develop
Browse files Browse the repository at this point in the history
Release
  • Loading branch information
evereq authored Dec 12, 2023
2 parents 859c6bd + cd4adbe commit 6a87ec6
Show file tree
Hide file tree
Showing 34 changed files with 454 additions and 217 deletions.
2 changes: 2 additions & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"ipsum",
"JITSU",
"kanban",
"kanbandata",
"Lorem",
"libappindicator",
"lucide",
Expand All @@ -74,6 +75,7 @@
"testid",
"Timesheet",
"tanstack",
"taskstatus",
"tempor",
"vcpu",
"Vercel",
Expand Down
74 changes: 37 additions & 37 deletions apps/mobile/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,65 +9,65 @@
* The app navigation resides in ./app/navigators, so head over there
* if you're interested in adding screens and navigators.
*/
import "./i18n"
import "./utils/ignoreWarnings"
import { useFonts } from "expo-font"
import React, { useEffect } from "react"
import { Provider as PaperProvider } from "react-native-paper"
import './i18n';
import './utils/ignoreWarnings';
import { useFonts } from 'expo-font';
import React, { useEffect } from 'react';
import { Provider as PaperProvider } from 'react-native-paper';

import { initialWindowMetrics, SafeAreaProvider } from "react-native-safe-area-context"
import { useInitialRootStore, useStores } from "./models"
import { AppNavigator, useNavigationPersistence } from "./navigators"
import { ErrorBoundary } from "./screens/ErrorScreen/ErrorBoundary"
import * as storage from "./utils/storage"
import { customDarkTheme, customFontsToLoad, customLightTheme } from "./theme"
import { setupReactotron } from "./services/reactotron"
import Config from "./config"
import { observer } from "mobx-react-lite"
import { initCrashReporting } from "./utils/crashReporting"
import FlashMessage from "react-native-flash-message"
import { ClickOutsideProvider } from "react-native-click-outside"
import { initialWindowMetrics, SafeAreaProvider } from 'react-native-safe-area-context';
import { useInitialRootStore, useStores } from './models';
import { AppNavigator, useNavigationPersistence } from './navigators';
import { ErrorBoundary } from './screens/ErrorScreen/ErrorBoundary';
import * as storage from './utils/storage';
import { customDarkTheme, customFontsToLoad, customLightTheme } from './theme';
import { setupReactotron } from './services/reactotron';
import Config from './config';
import { observer } from 'mobx-react-lite';
import { initCrashReporting } from './utils/crashReporting';
import FlashMessage from 'react-native-flash-message';
import { ClickOutsideProvider } from 'react-native-click-outside';

// Set up Reactotron, which is a free desktop app for inspecting and debugging
// React Native apps. Learn more here: https://github.com/infinitered/reactotron
setupReactotron({
// clear the Reactotron window when the app loads/reloads
clearOnLoad: true,
// generally going to be localhost
host: "localhost",
host: 'localhost',
// Reactotron can monitor AsyncStorage for you
useAsyncStorage: true,
// log the initial restored state from AsyncStorage
logInitialState: true,
// log out any snapshots as they happen (this is useful for debugging but slow)
logSnapshots: false,
})
logSnapshots: false
});

export const NAVIGATION_PERSISTENCE_KEY = "NAVIGATION_STATE"
export const NAVIGATION_PERSISTENCE_KEY = 'NAVIGATION_STATE';

interface AppProps {
hideSplashScreen: () => Promise<void>
hideSplashScreen: () => Promise<void>;
}

/**
* This is the root component of our app.
*/
const App = observer((props: AppProps) => {
const { hideSplashScreen } = props
const { hideSplashScreen } = props;
const {
initialNavigationState,
onNavigationStateChange,
isRestored: isNavigationStateRestored,
} = useNavigationPersistence(storage, NAVIGATION_PERSISTENCE_KEY)
isRestored: isNavigationStateRestored
} = useNavigationPersistence(storage, NAVIGATION_PERSISTENCE_KEY);
const {
authenticationStore: { isDarkMode },
} = useStores()
authenticationStore: { isDarkMode }
} = useStores();

useEffect(() => {
initCrashReporting() // To initialize Sentry.io
}, [])
initCrashReporting(); // To initialize Sentry.io
}, []);

const [areFontsLoaded] = useFonts(customFontsToLoad)
const [areFontsLoaded] = useFonts(customFontsToLoad);

const { rehydrated } = useInitialRootStore(() => {
// This runs after the root store has been initialized and rehydrated.
Expand All @@ -76,20 +76,20 @@ const App = observer((props: AppProps) => {
// Slightly delaying splash screen hiding for better UX; can be customized or removed as needed,
// Note: (vanilla Android) The splash-screen will not appear if you launch your app via the terminal or Android Studio. Kill the app and launch it normally by tapping on the launcher icon. https://stackoverflow.com/a/69831106
// Note: (vanilla iOS) You might notice the splash-screen logo change size. This happens in debug/development mode. Try building the app for release.
setTimeout(hideSplashScreen, 500)
})
setTimeout(hideSplashScreen, 500);
});

// Before we show the app, we have to wait for our state to be ready.
// In the meantime, don't render anything. This will be the background
// color set in native by rootView's background color.
// In iOS: application:didFinishLaunchingWithOptions:
// In Android: https://stackoverflow.com/a/45838109/204044
// You can replace with your own loading component if you wish.
if (!rehydrated || !isNavigationStateRestored || !areFontsLoaded) return null
if (!rehydrated || !isNavigationStateRestored || !areFontsLoaded) return null;

// otherwise, we're ready to render the app

const theme = isDarkMode ? customDarkTheme : customLightTheme
const theme = isDarkMode ? customDarkTheme : customLightTheme;
return (
<SafeAreaProvider initialMetrics={initialWindowMetrics}>
<PaperProvider theme={theme}>
Expand All @@ -105,6 +105,6 @@ const App = observer((props: AppProps) => {
</ErrorBoundary>
</PaperProvider>
</SafeAreaProvider>
)
})
export default App
);
});
export default App;
25 changes: 24 additions & 1 deletion apps/mobile/app/navigators/AppNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { StackScreenProps } from '@react-navigation/stack';
import { observer } from 'mobx-react-lite';
import React from 'react';
import React, { useEffect } from 'react';
import Config from '../config';
import { QueryClient, QueryClientProvider } from 'react-query';
import { useStores } from '../models'; // @demo remove-current-line
Expand All @@ -25,6 +25,8 @@ import {
} from './AuthenticatedNavigator';
import { DemoTabParamList } from './DemoNavigator'; // @demo remove-current-line
import { navigationRef, useBackButtonHandler } from './navigationUtilities';
import { Appearance } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';

/**
* This type allows TypeScript to know what routes are defined in this navigator
Expand Down Expand Up @@ -94,6 +96,27 @@ const AppStack = observer(function AppStack() {
interface NavigationProps extends Partial<React.ComponentProps<typeof NavigationContainer>> {}

export const AppNavigator = observer(function AppNavigator(props: NavigationProps) {
const {
authenticationStore: { isDarkMode, toggleTheme }
} = useStores();

useEffect(() => {
const checkAppInstallAndSetTheme = async () => {
const firstTimeAppOpen = await AsyncStorage.getItem('initialThemeSetupDone');

if (!firstTimeAppOpen) {
const colorsScheme = Appearance.getColorScheme();
if (colorsScheme === 'dark' && !isDarkMode) {
toggleTheme();
} else if (colorsScheme === 'light' && isDarkMode) {
toggleTheme();
}
await AsyncStorage.setItem('initialThemeSetupDone', JSON.stringify(true));
}
};
checkAppInstallAndSetTheme();
}, []);

useBackButtonHandler((routeName) => exitRoutes.includes(routeName));
const queryClient = new QueryClient();
return (
Expand Down
72 changes: 61 additions & 11 deletions apps/web/app/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { JitsuOptions } from '@jitsu/jitsu-react/dist/useJitsu';
import { I_SMTPRequest } from './interfaces/ISmtp';
import { getNextPublicEnv } from './env';

export const API_BASE_URL = '/api';
export const DEFAULT_APP_PATH = '/auth/passcode';
Expand Down Expand Up @@ -28,24 +29,47 @@ export const NO_TEAM_POPUP_SHOW_COOKIE_NAME = 'no-team-popup-show';
export const ACTIVE_PROJECT_COOKIE_NAME = 'auth-active-project';

// Recaptcha
export const RECAPTCHA_SITE_KEY = process.env.NEXT_PUBLIC_CAPTCHA_SITE_KEY;
export const RECAPTCHA_SITE_KEY = getNextPublicEnv(
'NEXT_PUBLIC_CAPTCHA_SITE_KEY',
process.env.NEXT_PUBLIC_CAPTCHA_SITE_KEY
);
export const RECAPTCHA_SECRET_KEY = process.env.CAPTCHA_SECRET_KEY;

// Gauzy Server URL
export const GAUZY_API_SERVER_URL = process.env.GAUZY_API_SERVER_URL || 'https://api.gauzy.co/api';
export const GAUZY_API_BASE_SERVER_URL = getNextPublicEnv(
'NEXT_PUBLIC_GAUZY_API_SERVER_URL',
process.env.NEXT_PUBLIC_GAUZY_API_SERVER_URL
);

// Invite
export const INVITE_CALLBACK_URL = process.env.INVITE_CALLBACK_URL || 'https://app.ever.team/auth/passcode';
export const INVITE_CALLBACK_PATH = '/auth/passcode';
export const VERIFY_EMAIL_CALLBACK_URL = process.env.VERIFY_EMAIL_CALLBACK_URL || 'https://app.ever.team/verify-email';
export const VERIFY_EMAIL_CALLBACK_PATH = '/verify-email';
export const GA_MEASUREMENT_ID = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID;
export const GA_MEASUREMENT_ID = getNextPublicEnv(
'NEXT_PUBLIC_GA_MEASUREMENT_ID',
process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID
);

// Chatwoot
export const CHATWOOT_API_KEY = getNextPublicEnv(
'NEXT_PUBLIC_CHATWOOT_API_KEY',
process.env.NEXT_PUBLIC_CHATWOOT_API_KEY
);

export const SMTP_FROM_ADDRESS = process.env.SMTP_FROM_ADDRESS || '[email protected]';
export const SMTP_HOST = process.env.SMTP_HOST || '';
export const SMTP_PORT = process.env.SMTP_PORT || '';
export const SMTP_SECURE = process.env.SMTP_SECURE || '';
export const SMTP_USERNAME = process.env.SMTP_USERNAME || '';
export const SMTP_PASSWORD = process.env.SMTP_PASSWORD || '';
export const DISABLE_AUTO_REFRESH = process.env.NEXT_PUBLIC_DISABLE_AUTO_REFRESH === 'true';
export const DISABLE_AUTO_REFRESH = getNextPublicEnv('NEXT_PUBLIC_DISABLE_AUTO_REFRESH', {
default: process.env.NEXT_PUBLIC_DISABLE_AUTO_REFRESH,
map(value) {
return value === 'true';
}
});

export const APP_NAME = process.env.APP_NAME || 'Ever Teams';
export const APP_SIGNATURE = process.env.APP_SIGNATURE || 'Ever Teams';
Expand All @@ -64,31 +88,53 @@ export const smtpConfiguration: () => I_SMTPRequest = () => ({
});

// Cookies
export const COOKIE_DOMAINS = (process.env.NEXT_PUBLIC_COOKIE_DOMAINS || 'ever.team').split(',').map((d) => d.trim());
export const COOKIE_DOMAINS = getNextPublicEnv('NEXT_PUBLIC_COOKIE_DOMAINS', {
default: process.env.NEXT_PUBLIC_COOKIE_DOMAINS || 'ever.team',
map(value) {
return value?.split(',').map((d) => d.trim()) || [];
}
});

// MEET Constants
export const MEET_DOMAIN = process.env.NEXT_PUBLIC_MEET_DOMAIN || 'meet.ever.team';
export const MEET_DOMAIN = getNextPublicEnv(
'NEXT_PUBLIC_MEET_DOMAIN',
process.env.NEXT_PUBLIC_MEET_DOMAIN || 'meet.ever.team'
);
export const MEET_JWT_APP_ID = process.env.MEET_JWT_APP_ID || 'ever_teams';
export const MEET_JWT_APP_SECRET = process.env.MEET_JWT_APP_SECRET;
export const MEET_JWT_TOKEN_COOKIE_NAME = 'meet-jwt-session';

// BOARD board
export const BOARD_APP_DOMAIN = process.env.NEXT_PUBLIC_BOARD_APP_DOMAIN || 'https://board.ever.team';
export const BOARD_BACKEND_POST_URL = process.env.NEXT_PUBLIC_BOARD_BACKEND_POST_URL || 'https://jsonboard.ever.team/api/v2/post/';
export const BOARD_FIREBASE_CONFIG = process.env.NEXT_PUBLIC_BOARD_FIREBASE_CONFIG;
export const BOARD_APP_DOMAIN = getNextPublicEnv(
'NEXT_PUBLIC_BOARD_APP_DOMAIN',
process.env.NEXT_PUBLIC_BOARD_APP_DOMAIN || 'https://board.ever.team'
);

export const BOARD_BACKEND_POST_URL = getNextPublicEnv(
'NEXT_PUBLIC_BOARD_BACKEND_POST_URL',
process.env.NEXT_PUBLIC_BOARD_BACKEND_POST_URL || 'https://jsonboard.ever.team/api/v2/post/'
);
export const BOARD_FIREBASE_CONFIG = getNextPublicEnv(
'NEXT_PUBLIC_BOARD_FIREBASE_CONFIG',
process.env.NEXT_PUBLIC_BOARD_FIREBASE_CONFIG
);

// Jitsu
export const jitsuConfiguration: () => JitsuOptions = () => ({
host: process.env.NEXT_PUBLIC_JITSU_BROWSER_URL || '',
writeKey: process.env.NEXT_PUBLIC_JITSU_BROWSER_WRITE_KEY || '',
host: getNextPublicEnv('NEXT_PUBLIC_JITSU_BROWSER_URL', process.env.NEXT_PUBLIC_JITSU_BROWSER_URL).value,
writeKey: getNextPublicEnv('NEXT_PUBLIC_JITSU_BROWSER_WRITE_KEY', process.env.NEXT_PUBLIC_JITSU_BROWSER_WRITE_KEY)
.value,
// if enabled - events will be sent to the console but no data sent to Jitsu.
// Strange this is not mentioned in the documentation https://github.com/jitsucom/jitsu/blob/35c4ecaff54d61a87853381cb17262b7bfbd4a6e/libs/jitsu-js/src/jitsu.ts#L40
echoEvents: false,
debug: false
});

// Github Integration
export const GITHUB_APP_NAME = process.env.NEXT_PUBLIC_GITHUB_APP_NAME || 'ever-github';
export const GITHUB_APP_NAME = getNextPublicEnv(
'NEXT_PUBLIC_GITHUB_APP_NAME',
process.env.NEXT_PUBLIC_GITHUB_APP_NAME || 'ever-github'
);

// Application Languages
export const APPLICATION_LANGUAGES = [
Expand Down Expand Up @@ -125,3 +171,7 @@ export enum IssuesView {
TABLE = 'TABLE',
BLOCKS = 'BLOCKS'
}

export const TaskStatus = {
INPROGRESS: 'in-progress'
}
65 changes: 65 additions & 0 deletions apps/web/app/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
const NEXT_PUBLIC_ENVS: { value: Env } = { value: {} };

type Env = Record<string, string | undefined>;

type OptionObject<T> = {
default?: string;
map?: (value: string | undefined) => T;
};
type Options<T> = string | OptionObject<T>;

type InferValue<T> = T extends { map: (value: any) => infer U } ? U : string | undefined;

type ReturnedType<T> = {
readonly value: T extends string ? string : InferValue<T>;
};

/**
* This function only loads environment variables starting with NEXT_PUBLIC_*
*
* Useful for getting the latest value of the variable at runtime rather than at build time
*
* @param name
* @param options
* @returns
*/
export function getNextPublicEnv<O extends Options<unknown>>(name: string, options?: O): ReturnedType<O> {
return {
get value() {
const defaultValue = typeof options === 'string' ? options : options?.default;

let value = NEXT_PUBLIC_ENVS.value[name] || defaultValue;
if (typeof options === 'object' && options.map) {
value = options.map(value) as any;
}

return value as any;
}
};
}

export function setNextPublicEnv(envs: Env) {
if (envs) {
NEXT_PUBLIC_ENVS.value = {
...NEXT_PUBLIC_ENVS.value,
...envs
};
}
}

export function loadNextPublicEnvs() {
return Object.keys(process.env)
.filter((key) => key.startsWith('NEXT_PUBLIC'))
.reduce(
(acc, value) => {
if (process.env[value]) {
acc[value] = process.env[value] as string;
}
return acc;
},
{} as Record<string, string>
);
}

// Preload Some variables
setNextPublicEnv(loadNextPublicEnvs());
Loading

0 comments on commit 6a87ec6

Please sign in to comment.