├── .gitignore
├── .husky
└── pre-commit
├── .huskyrc.json
├── assets
├── images
│ ├── icon.png
│ ├── favicon.png
│ ├── splash.png
│ ├── adaptive-icon.png
│ ├── welcome-logo.png
│ └── pawprint-wallpaper.png
└── fonts
│ └── SpaceMono-Regular.ttf
├── store
├── Auth.selector.ts
├── App.hooks.ts
├── Auth.action.ts
├── App.store.ts
└── Auth.reducer.ts
├── babel.config.js
├── .prettierrc.js
├── components
├── StyledText.tsx
├── __tests__
│ ├── StyledText-test.js
│ └── __snapshots__
│ │ ├── StyledText-test.js.snap
│ │ └── StyledText-test.tsx.snap
└── Themed.tsx
├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── new-feature.md
│ └── feature_request.md
├── constants
├── Layout.ts
└── Colors.ts
├── hooks
├── useColorScheme.ts
└── useCachedResources.ts
├── services
├── utils.ts
├── authService.ts
├── profileService.ts
└── userService.ts
├── screens
├── TabTwoScreen.tsx
├── NotFoundScreen.tsx
├── LoginPage.tsx
├── EditProfile.tsx
└── Profile.tsx
├── README.md
├── App.tsx
├── navigation
├── LinkingConfiguration.ts
└── index.tsx
├── .eslintrc.js
├── app.json
├── types.tsx
├── package.json
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
3 | yarn.lock
4 | .expo
5 | .env
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx --no-install lint-staged
5 |
--------------------------------------------------------------------------------
/.huskyrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "hooks": {
3 | "pre-commit": "npm run test -- --watchAll=false"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/assets/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sandboxnu/huskyhabits-app/main/assets/images/icon.png
--------------------------------------------------------------------------------
/assets/images/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sandboxnu/huskyhabits-app/main/assets/images/favicon.png
--------------------------------------------------------------------------------
/assets/images/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sandboxnu/huskyhabits-app/main/assets/images/splash.png
--------------------------------------------------------------------------------
/assets/images/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sandboxnu/huskyhabits-app/main/assets/images/adaptive-icon.png
--------------------------------------------------------------------------------
/assets/images/welcome-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sandboxnu/huskyhabits-app/main/assets/images/welcome-logo.png
--------------------------------------------------------------------------------
/assets/fonts/SpaceMono-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sandboxnu/huskyhabits-app/main/assets/fonts/SpaceMono-Regular.ttf
--------------------------------------------------------------------------------
/assets/images/pawprint-wallpaper.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sandboxnu/huskyhabits-app/main/assets/images/pawprint-wallpaper.png
--------------------------------------------------------------------------------
/store/Auth.selector.ts:
--------------------------------------------------------------------------------
1 | import { RootState } from "./App.store";
2 |
3 | export const selectCookies = (state: RootState): string => state.auth.cookies;
4 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function(api) {
2 | api.cache(true);
3 | return {
4 | presets: ['babel-preset-expo'],
5 | plugins: ['inline-dotenv'],
6 | };
7 | };
8 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | bracketSpacing: true,
3 | jsxBracketSameLine: true,
4 | singleQuote: true,
5 | trailingComma: 'all',
6 | // Override any other rules you want
7 | };
8 |
--------------------------------------------------------------------------------
/components/StyledText.tsx:
--------------------------------------------------------------------------------
1 | import { Text, TextProps } from './Themed';
2 |
3 | export function MonoText(props: TextProps) {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Expected Behavior
11 |
12 | ## Current Behavior
13 |
14 | ## Steps to reproduce
15 |
16 | 1.
17 |
--------------------------------------------------------------------------------
/constants/Layout.ts:
--------------------------------------------------------------------------------
1 | import { Dimensions } from 'react-native';
2 |
3 | const width = Dimensions.get('window').width;
4 | const height = Dimensions.get('window').height;
5 |
6 | export default {
7 | window: {
8 | width,
9 | height,
10 | },
11 | isSmallDevice: width < 375,
12 | };
13 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/new-feature.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: New feature
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Description
11 |
12 | ## Spec
13 |
14 | - [ ]
15 | - [ ]
16 | - [ ]
17 | - [ ]
18 |
19 | **Contact @ if you need help**
20 |
--------------------------------------------------------------------------------
/components/__tests__/StyledText-test.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import renderer from 'react-test-renderer';
3 |
4 | import { MonoText } from '../StyledText';
5 |
6 | it(`renders correctly`, () => {
7 | const tree = renderer.create(Snapshot test!).toJSON();
8 |
9 | expect(tree).toMatchSnapshot();
10 | });
11 |
--------------------------------------------------------------------------------
/store/App.hooks.ts:
--------------------------------------------------------------------------------
1 | import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
2 | import { AppDispatch, RootState } from "./App.store";
3 |
4 | // create types versions of useDispatch and useSelector
5 | export const useAppDispatch = () => useDispatch();
6 | export const useAppSelector: TypedUseSelectorHook = useSelector;
7 |
--------------------------------------------------------------------------------
/store/Auth.action.ts:
--------------------------------------------------------------------------------
1 |
2 | export namespace AuthAction {
3 | export enum Type {
4 | SET_COOKIES = 'SET_COOKIES'
5 | }
6 |
7 | interface SetCookies {
8 | type: typeof Type.SET_COOKIES,
9 | payload: string
10 | }
11 |
12 | export const setCookies = (payload: string): SetCookies => ({
13 | type: Type.SET_COOKIES,
14 | payload
15 | });
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/components/__tests__/__snapshots__/StyledText-test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`renders correctly 1`] = `
4 |
19 | Snapshot test!
20 |
21 | `;
22 |
--------------------------------------------------------------------------------
/components/__tests__/__snapshots__/StyledText-test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`renders correctly 1`] = `
4 |
19 | Snapshot test!
20 |
21 | `;
22 |
--------------------------------------------------------------------------------
/hooks/useColorScheme.ts:
--------------------------------------------------------------------------------
1 | import { ColorSchemeName, useColorScheme as _useColorScheme } from 'react-native';
2 |
3 | // The useColorScheme value is always either light or dark, but the built-in
4 | // type suggests that it can be null. This will not happen in practice, so this
5 | // makes it a bit easier to work with.
6 | export default function useColorScheme(): NonNullable {
7 | return _useColorScheme() as NonNullable;
8 | }
9 |
--------------------------------------------------------------------------------
/constants/Colors.ts:
--------------------------------------------------------------------------------
1 | const tintColorLight = '#2f95dc';
2 | const tintColorDark = '#fff';
3 |
4 | export default {
5 | light: {
6 | text: '#000',
7 | background: '#fff',
8 | tint: tintColorLight,
9 | tabIconDefault: '#ccc',
10 | tabIconSelected: tintColorLight,
11 | },
12 | dark: {
13 | text: '#fff',
14 | background: '#000',
15 | tint: tintColorDark,
16 | tabIconDefault: '#ccc',
17 | tabIconSelected: tintColorDark,
18 | },
19 | };
20 |
--------------------------------------------------------------------------------
/store/App.store.ts:
--------------------------------------------------------------------------------
1 | import { configureStore } from '@reduxjs/toolkit'
2 | import { AuthReducer } from './Auth.reducer';
3 |
4 | export const store = configureStore({
5 | reducer: {
6 | auth: AuthReducer.authReducer
7 | },
8 | });
9 |
10 | // this also might need to be changed
11 | export interface ActionType {
12 | type: string,
13 | payload: T
14 | }
15 |
16 | // infer Rootstate and AppDispatch types from the store itself
17 | export type RootState = ReturnType;
18 | export type AppDispatch = typeof store.dispatch;
19 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/services/utils.ts:
--------------------------------------------------------------------------------
1 | import { AxiosResponse } from 'axios';
2 |
3 | export interface ResponseEnvelope {
4 | isOK: boolean;
5 | message?: string;
6 | response?: T;
7 | }
8 |
9 | export function assert(condition: any, msg?: string): asserts condition {
10 | if (!condition) {
11 | throw new Error(msg);
12 | }
13 | }
14 |
15 | export function unwrapOrThrowError(
16 | response: AxiosResponse>,
17 | ignoreResponse = false,
18 | ): T {
19 | if (response.data.isOK) {
20 | if (ignoreResponse) {
21 | return {} as T;
22 | }
23 | assert(response.data.response);
24 | return response.data.response;
25 | }
26 | throw new Error(`Error processing request: ${response.data.message}`);
27 | }
28 |
--------------------------------------------------------------------------------
/screens/TabTwoScreen.tsx:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 |
3 | import { Text, View } from '../components/Themed';
4 |
5 | export default function TabTwoScreen() {
6 | return (
7 |
8 | Tab Two
9 |
10 |
11 | );
12 | }
13 |
14 | const styles = StyleSheet.create({
15 | container: {
16 | flex: 1,
17 | alignItems: 'center',
18 | justifyContent: 'center',
19 | },
20 | title: {
21 | fontSize: 20,
22 | fontWeight: 'bold',
23 | },
24 | separator: {
25 | marginVertical: 30,
26 | height: 1,
27 | width: '80%',
28 | },
29 | });
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Husky Habits App
2 |
3 | ## Setup
4 |
5 | Download [Yarn](https://classic.yarnpkg.com/lang/en/docs/install/#mac-stable), a JavaScript package manager.
6 |
7 | Install dependencies: `yarn install`
8 |
9 | ### React Native
10 |
11 | React Native is a JavaScript library based on React for building mobile user interfaces.
12 |
13 | [Expo](https://github.com/expo/expo-cli) is a platform for creating/deploying React Native apps.
14 |
15 | Install Expo CLI: `npm install -g expo-cli`
16 |
17 |
18 | ## Development
19 |
20 | With Expo, you can launch a mobile phone emulator for Husky Habits either on your computer or phone.
21 |
22 | ```bash
23 | yarn start # you can open iOS, Android, or web from here, or run them directly with the commands below:
24 | - yarn android
25 | - yarn ios
26 | - yarn web
27 | ```
--------------------------------------------------------------------------------
/store/Auth.reducer.ts:
--------------------------------------------------------------------------------
1 | import { AnyAction } from "redux";
2 | import { ActionType } from "./App.store";
3 | import { AuthAction } from "./Auth.action";
4 |
5 | export namespace AuthReducer {
6 | export interface State {
7 | cookies: string
8 | }
9 |
10 | const initialState: State = {
11 | cookies: ''
12 | }
13 |
14 | // todo: i think this needs to be a union or something
15 | export type Action = ActionType;
16 |
17 | export const authReducer = (state = initialState, action: AnyAction): State => {
18 | switch (action.type) {
19 | case AuthAction.Type.SET_COOKIES:
20 | return { ...state, cookies: String(action.payload) };
21 | default:
22 | return state;
23 | }
24 | }
25 |
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/App.tsx:
--------------------------------------------------------------------------------
1 | import { StatusBar } from 'expo-status-bar';
2 | import { SafeAreaProvider } from 'react-native-safe-area-context';
3 | import { Provider } from 'react-redux';
4 |
5 | import useCachedResources from './hooks/useCachedResources';
6 | import useColorScheme from './hooks/useColorScheme';
7 | import Navigation from './navigation';
8 | import { store } from './store/App.store';
9 |
10 | export default function App() {
11 | const isLoadingComplete = useCachedResources();
12 | const colorScheme = useColorScheme();
13 |
14 | if (!isLoadingComplete) {
15 | return null;
16 | } else {
17 | return (
18 |
19 |
20 |
21 |
22 |
23 |
24 | );
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/navigation/LinkingConfiguration.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Learn more about deep linking with React Navigation
3 | * https://reactnavigation.org/docs/deep-linking
4 | * https://reactnavigation.org/docs/configuring-links
5 | */
6 |
7 | import { LinkingOptions } from '@react-navigation/native';
8 | import * as Linking from 'expo-linking';
9 |
10 | import { RootStackParamList } from '../types';
11 |
12 | const linking: LinkingOptions = {
13 | prefixes: [Linking.createURL('/')],
14 | config: {
15 | screens: {
16 | Root: {
17 | screens: {
18 | Profile: {
19 | screens: {
20 | Profile: 'profile',
21 | EditProfile: 'modal',
22 | },
23 | },
24 | TabTwo: {
25 | screens: {
26 | TabTwoScreen: 'two',
27 | },
28 | },
29 | },
30 | },
31 | Login: 'login',
32 | NotFound: '*',
33 | },
34 | },
35 | };
36 |
37 | export default linking;
38 |
--------------------------------------------------------------------------------
/screens/NotFoundScreen.tsx:
--------------------------------------------------------------------------------
1 | import { StyleSheet, TouchableOpacity } from 'react-native';
2 |
3 | import { Text, View } from '../components/Themed';
4 | import { RootStackScreenProps } from '../types';
5 |
6 | export default function NotFoundScreen({ navigation }: RootStackScreenProps<'NotFound'>) {
7 | return (
8 |
9 | This screen doesn't exist.
10 | navigation.replace('Root')} style={styles.link}>
11 | Go to home screen!
12 |
13 |
14 | );
15 | }
16 |
17 | const styles = StyleSheet.create({
18 | container: {
19 | flex: 1,
20 | alignItems: 'center',
21 | justifyContent: 'center',
22 | padding: 20,
23 | },
24 | title: {
25 | fontSize: 20,
26 | fontWeight: 'bold',
27 | },
28 | link: {
29 | marginTop: 15,
30 | paddingVertical: 15,
31 | },
32 | linkText: {
33 | fontSize: 14,
34 | color: '#2e78b7',
35 | },
36 | });
37 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es2021: true,
5 | },
6 | extends: ['plugin:react/recommended', 'airbnb'],
7 | parser: '@typescript-eslint/parser',
8 | parserOptions: {
9 | ecmaFeatures: {
10 | jsx: true,
11 | },
12 | ecmaVersion: 'latest',
13 | sourceType: 'module',
14 | },
15 | plugins: ['react', 'react-native', '@typescript-eslint'],
16 | rules: {
17 | 'no-use-before-define': 'off',
18 | 'react/no-unstable-nested-components': 'off',
19 | 'react/jsx-uses-react': 'off',
20 | 'react/react-in-jsx-scope': 'off',
21 | 'react/jsx-props-no-spreading': 'off',
22 | 'react/jsx-filename-extension': 'off',
23 | 'import/extensions': 'off',
24 | 'import/no-unresolved': 'off',
25 | 'no-unstable-nested-components': 'off',
26 | '@typescript-eslint/no-unused-vars': [
27 | 'error',
28 | {
29 | args: 'none',
30 | },
31 | ],
32 | 'react/prop-types': [
33 | 'error',
34 | { ignore: ['navigation', 'navigation.navigate'] },
35 | ],
36 | },
37 | };
38 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "huskyhabits",
4 | "slug": "huskyhabits",
5 | "version": "1.0.0",
6 | "orientation": "portrait",
7 | "icon": "./assets/images/icon.png",
8 | "scheme": "exp",
9 | "userInterfaceStyle": "automatic",
10 | "splash": {
11 | "image": "./assets/images/splash.png",
12 | "resizeMode": "contain",
13 | "backgroundColor": "#ffffff"
14 | },
15 | "plugins": [
16 | [
17 | "expo-image-picker",
18 | {
19 | "photosPermission": "The app accesses your photos to let you share them with your friends."
20 | }
21 | ]
22 | ],
23 | "updates": {
24 | "fallbackToCacheTimeout": 0
25 | },
26 | "assetBundlePatterns": [
27 | "**/*"
28 | ],
29 | "ios": {
30 | "supportsTablet": true
31 | },
32 | "android": {
33 | "adaptiveIcon": {
34 | "foregroundImage": "./assets/images/adaptive-icon.png",
35 | "backgroundColor": "#ffffff"
36 | }
37 | },
38 | "web": {
39 | "favicon": "./assets/images/favicon.png"
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/hooks/useCachedResources.ts:
--------------------------------------------------------------------------------
1 | import { FontAwesome } from '@expo/vector-icons';
2 | import * as Font from 'expo-font';
3 | import * as SplashScreen from 'expo-splash-screen';
4 | import { useEffect, useState } from 'react';
5 |
6 | export default function useCachedResources() {
7 | const [isLoadingComplete, setLoadingComplete] = useState(false);
8 |
9 | // Load any resources or data that we need prior to rendering the app
10 | useEffect(() => {
11 | async function loadResourcesAndDataAsync() {
12 | try {
13 | SplashScreen.preventAutoHideAsync();
14 |
15 | // Load fonts
16 | await Font.loadAsync({
17 | ...FontAwesome.font,
18 | 'space-mono': require('../assets/fonts/SpaceMono-Regular.ttf'),
19 | });
20 | } catch (e) {
21 | // We might want to provide this error information to an error reporting service
22 | console.warn(e);
23 | } finally {
24 | setLoadingComplete(true);
25 | SplashScreen.hideAsync();
26 | }
27 | }
28 |
29 | loadResourcesAndDataAsync();
30 | }, []);
31 |
32 | return isLoadingComplete;
33 | }
34 |
--------------------------------------------------------------------------------
/services/authService.ts:
--------------------------------------------------------------------------------
1 | import axios, { AxiosInstance, AxiosResponse } from 'axios';
2 | import { assert } from './utils';
3 | import * as Linking from 'expo-linking';
4 | import * as WebBrowser from 'expo-web-browser';
5 | import { Buffer, constants } from 'buffer';
6 | import * as SecureStore from 'expo-secure-store';
7 | import { useAppDispatch } from '../store/App.hooks';
8 | import { AuthAction } from '../store/Auth.action';
9 |
10 | export default class AuthServiceClient {
11 | private _axios: AxiosInstance;
12 | private _baseURL: string;
13 |
14 | constructor(serviceUrl?: string) {
15 | const baseURL =
16 | 'http://' + process.env.BACKEND_URL || '';
17 | this._baseURL = baseURL;
18 | assert(baseURL);
19 | this._axios = axios.create({ baseURL });
20 | }
21 |
22 |
23 | async loginWithGoogle(redirectUri: string) {
24 | const url = `${this._baseURL}/auth/google${`?auth_redirect_uri=${redirectUri}`}`;
25 |
26 |
27 | try {
28 | const resp = await WebBrowser.openAuthSessionAsync(url, await Linking.getInitialURL() || '');
29 |
30 | if (resp.type == 'success') {
31 | const {queryParams} = Linking.parse(resp.url)
32 |
33 | const cookies = queryParams['cookies']
34 | await SecureStore.setItemAsync('auth-cookies', Buffer.from(cookies, 'base64').toString('ascii'))
35 | }
36 |
37 |
38 | } catch(e) {
39 | return new Error(`WARNING: could not open link: ${url}`);
40 | }
41 |
42 | // Below won't work in local testing (need a web browser to open google oauth page)
43 | // leaving here while still in early development
44 |
45 | // await this._axios.get(
46 | // `/google${redirectUri ? `?auth_redirect_uri=${redirectUri}` : ''}`);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/components/Themed.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Learn more about Light and Dark modes:
3 | * https://docs.expo.io/guides/color-schemes/
4 | */
5 |
6 | import { Text as DefaultText, View as DefaultView, TextInput as DefaultTextInput } from 'react-native';
7 |
8 | import Colors from '../constants/Colors';
9 | import useColorScheme from '../hooks/useColorScheme';
10 |
11 | export function useThemeColor(
12 | props: { light?: string; dark?: string },
13 | colorName: keyof typeof Colors.light & keyof typeof Colors.dark
14 | ) {
15 | const theme = useColorScheme();
16 | const colorFromProps = props[theme];
17 |
18 | if (colorFromProps) {
19 | return colorFromProps;
20 | } else {
21 | return Colors[theme][colorName];
22 | }
23 | }
24 |
25 | type ThemeProps = {
26 | lightColor?: string;
27 | darkColor?: string;
28 | };
29 |
30 | export type TextProps = ThemeProps & DefaultText['props'];
31 | export type ViewProps = ThemeProps & DefaultView['props'];
32 | export type TextInputProps = ThemeProps & DefaultTextInput['props'];
33 |
34 | export function Text(props: TextProps) {
35 | const { style, lightColor, darkColor, ...otherProps } = props;
36 | const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text');
37 |
38 | return ;
39 | }
40 |
41 | export function TextInput(props: TextInputProps) {
42 | const { style, lightColor, darkColor, ...otherProps } = props;
43 | const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text');
44 |
45 | return ;
46 | }
47 |
48 | export function View(props: ViewProps) {
49 | const { style, lightColor, darkColor, ...otherProps } = props;
50 | const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background');
51 |
52 | return ;
53 | }
54 |
--------------------------------------------------------------------------------
/types.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Learn more about using TypeScript with React Navigation:
3 | * https://reactnavigation.org/docs/typescript/
4 | */
5 |
6 | import { BottomTabScreenProps } from '@react-navigation/bottom-tabs';
7 | import {
8 | CompositeScreenProps,
9 | NavigatorScreenParams,
10 | } from '@react-navigation/native';
11 | import { NativeStackScreenProps } from '@react-navigation/native-stack';
12 |
13 | declare global {
14 | namespace ReactNavigation {
15 | interface RootParamList extends RootStackParamList {}
16 | }
17 | }
18 |
19 | export type AuthStackParamList = {
20 | Root: NavigatorScreenParams | undefined;
21 | EditProfile: undefined;
22 | NotFound: undefined;
23 | }
24 |
25 | export type AuthStackScreenProps = {
26 | }
27 |
28 | export type RootStackParamList = {
29 | Root: NavigatorScreenParams | undefined;
30 | Profile: undefined;
31 | EditProfile: undefined;
32 | Login: undefined;
33 | NotFound: undefined;
34 | };
35 |
36 | export type RootStackScreenProps =
37 | NativeStackScreenProps;
38 |
39 | export type AuthTabParamList = {
40 | Profile: undefined;
41 | TabTwo: undefined;
42 | };
43 |
44 | export type RootModalParamList = {
45 | Profile: undefined;
46 | EditProfile: undefined;
47 | }
48 |
49 | export type RootParamList = {
50 | Login: undefined;
51 | }
52 |
53 | export type RootScreenProps =
54 | NativeStackScreenProps;
55 |
56 | export type RootStackModalProps =
57 | NativeStackScreenProps;
58 |
59 | export type RootTabScreenProps =
60 | CompositeScreenProps<
61 | BottomTabScreenProps,
62 | NativeStackScreenProps
63 | >;
--------------------------------------------------------------------------------
/services/profileService.ts:
--------------------------------------------------------------------------------
1 | import axios, { AxiosInstance } from 'axios';
2 | import { assert } from './utils';
3 | import { ResponseEnvelope, unwrapOrThrowError } from './utils';
4 |
5 | export interface CreateProfileRequest {
6 | username: string;
7 | bio?: string;
8 | }
9 |
10 | export interface CreateProfileResponse {
11 | profileId: string;
12 | }
13 |
14 | export interface GetProfileRequest {
15 | profileId: string;
16 | }
17 |
18 | export interface GetProfileResponse {
19 | userId: string;
20 | username: string;
21 | bio: string;
22 | photo: { data: Buffer; contentType: string };
23 | }
24 |
25 | export interface GetProfileFriendsRequest {
26 | profileId: string;
27 | }
28 |
29 | export interface GetProfileFriendsResponse {
30 | friends: [
31 | {
32 | username: string;
33 | bio: string;
34 | photo: { data: Buffer; contentType: string };
35 | },
36 | ];
37 | }
38 |
39 | export default class ProfileServicesClient {
40 | private _axios: AxiosInstance;
41 |
42 | constructor(serviceUrl?: string) {
43 | const baseURL =
44 | serviceUrl ||
45 | `http://${process.env.BACKEND_URL}/v1/profiles`;
46 | assert(baseURL);
47 | this._axios = axios.create({ baseURL });
48 | }
49 |
50 | async createProfile(
51 | requestData: CreateProfileRequest,
52 | ): Promise {
53 | const responseWrapper = await this._axios.post<
54 | ResponseEnvelope
55 | >('/', requestData);
56 | return unwrapOrThrowError(responseWrapper);
57 | }
58 |
59 | async getProfileById(
60 | requestData: GetProfileRequest,
61 | ): Promise {
62 | const responseWrapper = await this._axios.get<
63 | ResponseEnvelope
64 | >(`/${requestData.profileId}`);
65 | return unwrapOrThrowError(responseWrapper);
66 | }
67 |
68 | async getFriendsForProfile(
69 | requestData: GetProfileFriendsRequest,
70 | ): Promise {
71 | const responseWrapper = await this._axios.get<
72 | ResponseEnvelope
73 | >(`/${requestData.profileId}/friends`);
74 | return unwrapOrThrowError(responseWrapper);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/services/userService.ts:
--------------------------------------------------------------------------------
1 | import axios, { AxiosInstance, AxiosResponse } from 'axios';
2 | import { assert } from './utils';
3 | import { ResponseEnvelope, unwrapOrThrowError } from './utils';
4 |
5 | export interface GetUserResponse {
6 | userId: string;
7 | email: string;
8 | firstName: string;
9 | lastName: string;
10 | accounts: [{ acc_type: string; uid: string }];
11 | }
12 |
13 | export interface GetUserChallengesResponse {
14 | // TODO
15 | }
16 |
17 | export interface GetUserFriendRequestsResponse {
18 | // TODO
19 | }
20 |
21 | export interface GetUserAvatarRequest {
22 | userId: string;
23 | size: 'sm' | 'md' | 'lg';
24 | }
25 |
26 | export interface GetUserAvatarResponse {
27 | // TODO
28 | }
29 |
30 | export default class UserServiceClient {
31 | private _axios: AxiosInstance;
32 |
33 | constructor(serviceUrl?: string) {
34 | const baseURL =
35 | serviceUrl ||
36 | `http://${process.env.BACKEND_URL}/v1/users`;
37 | assert(baseURL);
38 | this._axios = axios.create({ baseURL });
39 | }
40 |
41 | async getUserById(requestData: { userId: string }): Promise {
42 | const responseWrapper = await this._axios.get<
43 | ResponseEnvelope
44 | >(`/${requestData.userId}`);
45 | return unwrapOrThrowError(responseWrapper);
46 | }
47 |
48 | async getUserChallenges(requestData: {
49 | userId: string;
50 | }): Promise {
51 | const responseWrapper = await this._axios.get<
52 | ResponseEnvelope
53 | >(`/${requestData.userId}/challenges`);
54 | return unwrapOrThrowError(responseWrapper);
55 | }
56 |
57 | async getUserFriendRequests(requestData: {
58 | userId: string;
59 | }): Promise {
60 | const responseWrapper = await this._axios.get<
61 | ResponseEnvelope
62 | >(`/${requestData.userId}/friend_requests`);
63 | return unwrapOrThrowError(responseWrapper);
64 | }
65 |
66 | async getUserAvatar(
67 | requestData: GetUserAvatarRequest,
68 | ): Promise {
69 | const responseWrapper = await this._axios.get<
70 | ResponseEnvelope
71 | >(`/users/${requestData.userId}/avatar?size=${requestData.size}`);
72 | return unwrapOrThrowError(responseWrapper);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "huskyhabits-app",
3 | "version": "1.0.0",
4 | "main": "node_modules/expo/AppEntry.js",
5 | "scripts": {
6 | "start": "expo start",
7 | "android": "expo start --android",
8 | "ios": "expo start --ios",
9 | "web": "expo start --web",
10 | "eject": "expo eject",
11 | "test": "jest --watchAll"
12 | },
13 | "jest": {
14 | "preset": "jest-expo"
15 | },
16 | "dependencies": {
17 | "@expo/vector-icons": "^12.0.0",
18 | "@react-navigation/bottom-tabs": "^6.0.5",
19 | "@react-navigation/native": "^6.0.2",
20 | "@react-navigation/native-stack": "^6.1.0",
21 | "@reduxjs/toolkit": "^1.8.0",
22 | "@types/react-redux": "^7.1.23",
23 | "axios": "^0.26.1",
24 | "babel-plugin-inline-dotenv": "^1.7.0",
25 | "buffer": "^6.0.3",
26 | "expo": "~44.0.0",
27 | "expo-asset": "~8.4.4",
28 | "expo-auth-session": "~3.5.0",
29 | "expo-font": "~10.0.4",
30 | "expo-image-picker": "~12.0.1",
31 | "expo-linking": "~3.0.0",
32 | "expo-secure-store": "~11.1.0",
33 | "expo-splash-screen": "~0.14.0",
34 | "expo-status-bar": "~1.2.0",
35 | "expo-web-browser": "~10.1.0",
36 | "react": "17.0.1",
37 | "react-dom": "17.0.1",
38 | "react-native": "0.64.3",
39 | "react-native-elements": "^3.4.2",
40 | "react-native-image-picker": "^4.7.3",
41 | "react-native-keyboard-aware-scroll-view": "^0.9.5",
42 | "react-native-safe-area-context": "3.3.2",
43 | "react-native-screens": "~3.10.1",
44 | "react-native-web": "0.17.1",
45 | "react-redux": "^7.2.6",
46 | "redux": "^4.1.2"
47 | },
48 | "devDependencies": {
49 | "@babel/core": "^7.12.9",
50 | "@types/react": "^17.0.38",
51 | "@types/react-native": "^0.66.15",
52 | "@typescript-eslint/eslint-plugin": "^5.10.1",
53 | "@typescript-eslint/parser": "^5.10.1",
54 | "eslint": "^8.7.0",
55 | "eslint-config-airbnb": "^19.0.4",
56 | "eslint-plugin-import": "^2.25.4",
57 | "eslint-plugin-jsx-a11y": "^6.5.1",
58 | "eslint-plugin-react": "^7.28.0",
59 | "eslint-plugin-react-hooks": "^4.3.0",
60 | "eslint-plugin-react-native": "^4.0.0",
61 | "husky": "^7.0.4",
62 | "jest": "^26.6.3",
63 | "jest-expo": "~44.0.1",
64 | "lint-staged": "^12.3.1",
65 | "prettier": "^2.5.1",
66 | "react-native-typescript-transformer": "^1.2.13",
67 | "react-test-renderer": "17.0.1",
68 | "ts-jest": "^27.1.3",
69 | "typescript": "^4.5.5"
70 | },
71 | "lint-staged": {
72 | "*.{js,jsx,ts,tsx}": [
73 | "prettier --write",
74 | "eslint --fix"
75 | ]
76 | },
77 | "private": true
78 | }
79 |
--------------------------------------------------------------------------------
/screens/LoginPage.tsx:
--------------------------------------------------------------------------------
1 | import { ImageBackground, Button, ScrollView } from "react-native"
2 | import { Image, StyleSheet } from 'react-native';
3 | import {SocialIcon} from 'react-native-elements';
4 | import { Text, View, TextInput } from '../components/Themed';
5 | import React, { useEffect } from 'react';
6 | import AuthServiceClient from '../services/authService';
7 | import * as Linking from 'expo-linking';
8 | import { RootStackScreenProps } from "../types";
9 | import { useAppDispatch } from "../store/App.hooks";
10 | import * as SecureStore from 'expo-secure-store';
11 | import { AuthAction } from "../store/Auth.action";
12 |
13 | export default function Login({ navigation }: RootStackScreenProps<'Login'>) {
14 | const authClient: AuthServiceClient = new AuthServiceClient();
15 | const dispatch = useAppDispatch();
16 |
17 | const handleAuth = async () => {
18 | const initialUrl = await Linking.getInitialURL() as string;
19 | const oAuthLogin = await authClient.loginWithGoogle(initialUrl);
20 | // returns error
21 | if (oAuthLogin) {
22 | console.log("OAuth failed");
23 | }
24 |
25 | const cookies = await SecureStore.getItemAsync('auth-cookies');
26 |
27 | dispatch(AuthAction.setCookies(cookies || ''));
28 | };
29 |
30 | return (
31 |
32 |
33 |
34 |
40 |
41 | {/* */}
42 |
43 | );
44 | }
45 |
46 | const styles = StyleSheet.create({
47 | container: {
48 | flex: 1,
49 | padding: 1
50 | },
51 | pageContainer: {
52 | alignItems: 'center',
53 | justifyContent: 'center',
54 | flex: 1,
55 | },
56 | input: {
57 | height: 40,
58 | width: 200,
59 | margin: 5,
60 | borderWidth: 1,
61 | padding: 10,
62 | },
63 | multilineInput: {
64 | height: 100,
65 | width: 200,
66 | margin: 5,
67 | borderWidth: 1,
68 | padding: 10,
69 | },
70 | inputContainer: {
71 | flexDirection: "row",
72 | alignItems: "center",
73 | },
74 | profileImage: {
75 | width: 100,
76 | height: 100,
77 | borderRadius: 50,
78 | borderColor: 'black',
79 | borderWidth: 1,
80 | },
81 | image: {
82 | flex: 1,
83 | justifyContent: "center"
84 | },
85 | photoContainer: {
86 | flexDirection: "column",
87 | alignItems: 'center',
88 | justifyContent: 'center',
89 | margin: 5,
90 | },
91 | profileContainer: {
92 | paddingVertical: 20,
93 | alignItems: 'center',
94 | },
95 | changeImageLabel: {
96 | marginTop: 5,
97 | },
98 | textLabel: {
99 | textAlign: "right",
100 | width: 100,
101 | fontSize: 15,
102 | marginRight: 10,
103 | padding: 0,
104 | },
105 | title: {
106 | fontSize: 30,
107 | fontWeight: 'bold',
108 | margin: 20,
109 | textAlign: 'center',
110 | },
111 | separator: {
112 | marginVertical: 15,
113 | height: 1,
114 | width: '80%',
115 | },
116 | });
117 |
--------------------------------------------------------------------------------
/screens/EditProfile.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { Image, StyleSheet } from 'react-native';
3 | import { Text, View, TextInput } from '../components/Themed';
4 | import * as ImagePicker from 'expo-image-picker';
5 | import { Buffer } from 'buffer';
6 | import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
7 |
8 |
9 | export default function EditProfile() {
10 | const [username, setUsername] = useState("");
11 | const [firstName, setFirstName] = useState("");
12 | const [lastName, setLastName] = useState("");
13 | const [bio, setBio] = useState("");
14 | const [photoBuffer, setPhotoBuffer] = useState(null);
15 | const [photoURI, setPhotoURI] = useState("");
16 |
17 | const onChangeImage = async () => {
18 | let result = await ImagePicker.launchImageLibraryAsync({
19 | mediaTypes: ImagePicker.MediaTypeOptions.All,
20 | allowsEditing: true,
21 | base64: true,
22 | aspect: [1, 1],
23 | quality: 1,
24 | });
25 |
26 | if (!result.cancelled) {
27 | if (result.base64) {
28 | const buffer: Buffer = Buffer.from(result.base64, "base64");
29 | setPhotoURI("data:image/jpeg;base64,"+result.base64);
30 | setPhotoBuffer(buffer);
31 | }
32 | }
33 | }
34 |
35 | return (
36 |
37 |
38 |
39 |
45 |
50 | Change profile photo
51 |
52 |
53 |
58 |
59 | Username
60 |
67 |
68 |
69 | First Name
70 |
77 |
78 |
79 | Last Name
80 |
88 |
89 |
90 | Bio
91 |
102 |
103 |
104 |
105 | );
106 | }
107 |
108 | const styles = StyleSheet.create({
109 | container: {
110 | flex: 1,
111 | },
112 | input: {
113 | height: 40,
114 | width: 200,
115 | margin: 5,
116 | borderWidth: 1,
117 | padding: 10,
118 | },
119 | multilineInput: {
120 | height: 100,
121 | width: 200,
122 | margin: 5,
123 | borderWidth: 1,
124 | padding: 10,
125 | },
126 | inputContainer: {
127 | flexDirection: "row",
128 | alignItems: "center",
129 | },
130 | profileImage: {
131 | width: 100,
132 | height: 100,
133 | borderRadius: 50,
134 | borderColor: 'black',
135 | borderWidth: 1,
136 | },
137 | photoContainer: {
138 | flexDirection: "column",
139 | alignItems: 'center',
140 | justifyContent: 'center',
141 | margin: 5,
142 | },
143 | profileContainer: {
144 | paddingVertical: 20,
145 | alignItems: 'center',
146 | },
147 | changeImageLabel: {
148 | marginTop: 5,
149 | },
150 | textLabel: {
151 | textAlign: "right",
152 | width: 100,
153 | fontSize: 15,
154 | marginRight: 10,
155 | padding: 0,
156 | },
157 | title: {
158 | fontSize: 20,
159 | fontWeight: 'bold',
160 | },
161 | separator: {
162 | marginVertical: 15,
163 | height: 1,
164 | width: '80%',
165 | },
166 | });
167 |
--------------------------------------------------------------------------------
/screens/Profile.tsx:
--------------------------------------------------------------------------------
1 | import { StyleSheet, Image, ScrollView } from 'react-native';
2 | import { FontAwesome } from '@expo/vector-icons';
3 | import { Text, View } from '../components/Themed';
4 | import { Button } from 'react-native';
5 | import * as SecureStore from 'expo-secure-store';
6 | import { useAppDispatch } from '../store/App.hooks';
7 | import { AuthAction } from '../store/Auth.action';
8 |
9 | export default function ProfileScreen() {
10 | const dispatch = useAppDispatch();
11 |
12 |
13 | const logout = () => {
14 | SecureStore.deleteItemAsync('auth-cookies').then(() => {
15 | alert('Logged out');
16 | })
17 |
18 | dispatch(AuthAction.setCookies(''));
19 | }
20 |
21 | return (
22 |
23 |
24 |
30 | Ross Newman
31 | @ross3102
32 |
33 |
34 | Boston, MA, USA
35 |
36 |
37 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
38 | eiusmod tempor incididunt ut labore et dolore magna aliqua.
39 |
40 |
41 |
47 |
48 |
53 |
54 |
55 | Groups
56 |
57 |
63 |
64 | Group 1
65 | 10 members
66 |
67 |
68 |
69 |
75 |
76 | Group 2
77 | 150 members
78 |
79 |
80 |
81 |
87 |
88 | Group 3
89 | 2 members
90 |
91 |
92 |
93 |
99 |
100 | Group 4
101 | 50 members
102 |
103 |
104 |
105 |
106 |
107 | );
108 | }
109 |
110 | const styles = StyleSheet.create({
111 | container: {
112 | flex: 1,
113 | },
114 | profileContainer: {
115 | alignItems: 'center',
116 | justifyContent: 'center',
117 | backgroundColor: '#666',
118 | padding: 10,
119 | margin: 10,
120 | },
121 | title: {
122 | fontSize: 25,
123 | fontWeight: 'bold',
124 | },
125 | handle: {
126 | fontSize: 20,
127 | color: '#999',
128 | },
129 | locationView: {
130 | alignItems: 'center',
131 | justifyContent: 'center',
132 | backgroundColor: '#666',
133 | flexDirection: 'row',
134 | marginBottom: 10,
135 | marginTop: 5,
136 | },
137 | bio: {
138 | textAlign: 'center',
139 | },
140 | profileImage: {
141 | width: 100,
142 | height: 100,
143 | borderRadius: 50,
144 | borderColor: 'black',
145 | borderWidth: 1,
146 | },
147 | groupsContainer: {
148 | alignItems: 'center',
149 | justifyContent: 'center',
150 | backgroundColor: '#666',
151 | width: '100%',
152 | },
153 | separator: {
154 | marginVertical: 30,
155 | height: 1,
156 | width: '80%',
157 | },
158 | group: {
159 | alignItems: 'center',
160 | flexDirection: 'row',
161 | backgroundColor: '#aaa',
162 | borderColor: 'black',
163 | borderWidth: 1,
164 | width: '100%',
165 | marginTop: 10,
166 | },
167 | groupImage: {
168 | width: 100,
169 | height: 100,
170 | },
171 | groupInfo: {
172 | backgroundColor: '#aaa',
173 | margin: 10,
174 | },
175 | groupName: {
176 | fontSize: 20,
177 | fontWeight: 'bold',
178 | },
179 | });
180 |
--------------------------------------------------------------------------------
/navigation/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * If you are not familiar with React Navigation, refer to the "Fundamentals" guide:
3 | * https://reactnavigation.org/docs/getting-started
4 | *
5 | */
6 | import { FontAwesome } from '@expo/vector-icons';
7 | import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
8 | import {
9 | NavigationContainer,
10 | DefaultTheme,
11 | DarkTheme,
12 | } from '@react-navigation/native';
13 | import { createNativeStackNavigator } from '@react-navigation/native-stack';
14 | import * as React from 'react';
15 | import { Button, ColorSchemeName, Pressable } from 'react-native';
16 | import useColorScheme from '../hooks/useColorScheme';
17 | import LoginPage from '../screens/LoginPage';
18 | import EditProfile from '../screens/EditProfile';
19 | import NotFoundScreen from '../screens/NotFoundScreen';
20 | import ProfileScreen from '../screens/Profile';
21 | import TabTwoScreen from '../screens/TabTwoScreen';
22 | import {
23 | RootStackModalProps,
24 | AuthStackParamList,
25 | AuthStackScreenProps,
26 | AuthTabParamList,
27 | RootTabScreenProps,
28 | RootScreenProps,
29 | RootParamList,
30 | } from '../types';
31 | import LinkingConfiguration from './LinkingConfiguration';
32 | import * as SecureStore from 'expo-secure-store';
33 | import { useSelector } from 'react-redux';
34 | import { selectCookies } from '../store/Auth.selector';
35 |
36 | export default function Navigation({
37 | colorScheme,
38 | }: {
39 | colorScheme: ColorSchemeName;
40 | }) {
41 |
42 | // const isUser: boolean = false
43 | const [authenticated, setAuthenticated] = React.useState(false);
44 | const cookies = useSelector(selectCookies);
45 |
46 | React.useEffect(() => {
47 | setAuthenticated(cookies !== '');
48 |
49 | }, [ cookies ]);
50 |
51 | return (
52 |
56 | {authenticated ? : }
57 |
58 | );
59 | }
60 |
61 | /**
62 | * AuthNavigator holds all screens for authenticated users.
63 | */
64 | const AuthStack = createNativeStackNavigator();
65 |
66 | function AuthNavigator() {
67 | return (
68 |
69 |
73 |
78 |
79 | ) => ({
83 | title: 'Edit Profile',
84 | headerRight: () => (
85 |
93 |
94 | );
95 | }
96 |
97 | /**
98 | * RegisterNavigator holds all screens for unauthenticated users.
99 | */
100 | const RootStack = createNativeStackNavigator();
101 |
102 | function RegisterNavigator() {
103 | return (
104 |
105 |
110 |
111 | );
112 | }
113 |
114 | /**
115 | * A bottom tab navigator displays tab buttons on the bottom of the display to switch screens.
116 | * https://reactnavigation.org/docs/bottom-tab-navigator
117 | */
118 | const BottomTab = createBottomTabNavigator();
119 |
120 | function BottomTabNavigator() {
121 | const colorScheme = useColorScheme();
122 |
123 | return (
124 |
129 | ) => ({
133 | title: 'Profile',
134 | tabBarIcon: ({ color }) => ,
135 | headerRight: () => (
136 | navigation.navigate('EditProfile')}
138 | style={({ pressed }) => ({
139 | opacity: pressed ? 0.5 : 1,
140 | })}
141 | >
142 |
147 |
148 | ),
149 | })}
150 | />
151 | {/* ,
157 | }}
158 | /> */}
159 |
160 | );
161 | }
162 |
163 | /**
164 | * You can explore the built-in icon families and icons on the web at https://icons.expo.fyi/
165 | */
166 | function TabBarIcon(props: {
167 | name: React.ComponentProps['name'];
168 | color: string;
169 | }) {
170 | return ;
171 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "expo/tsconfig.base",
3 | "compilerOptions": {
4 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
5 |
6 | /* Projects */
7 | // "incremental": true, /* Enable incremental compilation */
8 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
9 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
10 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
11 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
12 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
13 |
14 | /* Language and Environment */
15 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
16 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
17 | "jsx": "react-jsx", /* Specify what JSX code is generated. */
18 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
19 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
20 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
21 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
22 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
23 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
24 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
25 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
26 |
27 | /* Modules */
28 | "module": "commonjs", /* Specify what module code is generated. */
29 | // "rootDir": "src", /* Specify the root folder within your source files. */
30 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
34 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */
36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
37 | // "resolveJsonModule": true, /* Enable importing .json files */
38 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */
39 |
40 | /* JavaScript Support */
41 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
42 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
43 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
44 |
45 | /* Emit */
46 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
47 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */
48 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
49 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
50 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
51 | // "outDir": "./", /* Specify an output folder for all emitted files. */
52 | // "removeComments": true, /* Disable emitting comments. */
53 | // "noEmit": true, /* Disable emitting files from a compilation. */
54 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
55 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
56 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
57 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
58 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
59 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
60 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
61 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
62 | // "newLine": "crlf", /* Set the newline character for emitting files. */
63 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
64 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
65 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
66 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
67 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
68 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
69 |
70 | /* Interop Constraints */
71 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
72 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
73 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
74 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
75 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
76 |
77 | /* Type Checking */
78 | "strict": true, /* Enable all strict type-checking options. */
79 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
80 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
81 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
82 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
83 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
84 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
85 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
86 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
87 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
88 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
89 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
90 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
91 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
92 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
93 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
94 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
95 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
96 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
97 |
98 | /* Completeness */
99 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
100 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
101 | }
102 | }
103 |
--------------------------------------------------------------------------------