From 091606f1ca559d78fd4370f5d22bd3d82c7ded43 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 8 Oct 2025 08:57:14 -0700 Subject: [PATCH] Clean up, reimplement native paper, and sign in screen. --- App.js | 2 +- app/_layout.tsx | 88 +++- app/index.tsx | 16 +- app/settings.tsx | 9 +- app/sign-in.tsx | 4 + assets/{ => images}/logo192.png | Bin components/external-link.tsx | 25 -- components/haptic-tab.tsx | 18 - components/hello-wave.tsx | 19 - components/parallax-scroll-view.tsx | 79 ---- components/sign-in/sign-in-error.jsx | 53 +++ components/sign-in/sign-in.jsx | 132 ++++++ components/themed-text.tsx | 60 --- components/themed-view.tsx | 14 - components/ui/collapsible.tsx | 45 -- components/ui/icon-symbol.ios.tsx | 32 -- components/ui/icon-symbol.tsx | 41 -- package-lock.json | 222 +++++++++- package.json | 50 +-- translations/en-US/common.json | 617 ++++++++++++++------------- 20 files changed, 839 insertions(+), 687 deletions(-) create mode 100644 app/sign-in.tsx rename assets/{ => images}/logo192.png (100%) delete mode 100644 components/external-link.tsx delete mode 100644 components/haptic-tab.tsx delete mode 100644 components/hello-wave.tsx delete mode 100644 components/parallax-scroll-view.tsx create mode 100644 components/sign-in/sign-in-error.jsx create mode 100644 components/sign-in/sign-in.jsx delete mode 100644 components/themed-text.tsx delete mode 100644 components/themed-view.tsx delete mode 100644 components/ui/collapsible.tsx delete mode 100644 components/ui/icon-symbol.ios.tsx delete mode 100644 components/ui/icon-symbol.tsx diff --git a/App.js b/App.js index 3031c6f..7697b7c 100644 --- a/App.js +++ b/App.js @@ -1,6 +1,6 @@ -import 'expo-dev-client'; import { ApolloProvider } from "@apollo/client"; import "expo-asset"; +import 'expo-dev-client'; import "intl"; import "intl/locale-data/jsonp/en"; import { SafeAreaProvider } from "react-native-safe-area-context"; diff --git a/app/_layout.tsx b/app/_layout.tsx index 620ea20..df04d73 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -1,29 +1,93 @@ +import { checkUserSession } from "@/redux/user/user.actions"; +import { selectBodyshop, selectCurrentUser } from "@/redux/user/user.selectors"; import { ApolloProvider } from "@apollo/client"; +import { Stack } from "expo-router"; import { Icon, Label, NativeTabs } from "expo-router/unstable-native-tabs"; +import { useEffect } from "react"; import { useTranslation } from "react-i18next"; -import { Provider } from "react-redux"; +import { ActivityIndicator, View } from "react-native"; +import { MD3LightTheme, Provider as PaperProvider } from "react-native-paper"; +import { connect, Provider } from "react-redux"; import { PersistGate } from "redux-persist/integration/react"; +import { createStructuredSelector } from "reselect"; import { client } from "../graphql/client"; import { persistor, store } from "../redux/store"; import "../translations/i18n"; -export default function TabLayout() { +function AuthenticatedLayout() { const { t } = useTranslation(); + return ( + + + + + + + + + + + ); +} +function UnauthenticatedLayout() { + return ( + + + + ); +} + +function LoadingLayout() { + return ( + + + + ); +} + +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop, + currentUser: selectCurrentUser, +}); +const mapDispatchToProps = (dispatch) => ({ + checkUserSession: () => dispatch(checkUserSession()), +}); +function AppContent({ currentUser, checkUserSession, bodyshop }) { + useEffect(() => { + checkUserSession(); + }, []); + + if (currentUser.authorized === null) { + return ; + } + if (currentUser.authorized) { + return ; + } + return ; +} +const ConnectedAppContent = connect( + mapStateToProps, + mapDispatchToProps +)(AppContent); + +const theme = { + ...MD3LightTheme, + colors: { + ...MD3LightTheme.colors, + primary: "#1890ff", + accent: "tomato", + }, +}; + +export default function AppLayout() { return ( - - - - - - - - - - + + + diff --git a/app/index.tsx b/app/index.tsx index 127e318..e006d8e 100644 --- a/app/index.tsx +++ b/app/index.tsx @@ -1,5 +1,17 @@ +import { selectCurrentUser } from "@/redux/user/user.selectors"; import { Redirect } from "expo-router"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; -export default function Index() { - return ; +const mapStateToProps = createStructuredSelector({ + currentUser: selectCurrentUser, +}); + +function Index({ currentUser }) { + if (currentUser.authorized) { + return ; + } + + return ; } +export default connect(mapStateToProps, null)(Index); diff --git a/app/settings.tsx b/app/settings.tsx index 2aa5299..da1362b 100644 --- a/app/settings.tsx +++ b/app/settings.tsx @@ -1,9 +1,10 @@ -import { StyleSheet, Text, View } from 'react-native'; - +import SignOutButton from "@/components-old/sign-out-button/sign-out-button.component"; +import { StyleSheet, Text, View } from "react-native"; export default function Tab() { return ( Tab [Home|Settings] + ); } @@ -11,7 +12,7 @@ export default function Tab() { const styles = StyleSheet.create({ container: { flex: 1, - justifyContent: 'center', - alignItems: 'center', + justifyContent: "center", + alignItems: "center", }, }); diff --git a/app/sign-in.tsx b/app/sign-in.tsx new file mode 100644 index 0000000..e72bcf8 --- /dev/null +++ b/app/sign-in.tsx @@ -0,0 +1,4 @@ +import SignIn from "@/components/sign-in/sign-in"; +export default function SignInScreen() { + return ; +} diff --git a/assets/logo192.png b/assets/images/logo192.png similarity index 100% rename from assets/logo192.png rename to assets/images/logo192.png diff --git a/components/external-link.tsx b/components/external-link.tsx deleted file mode 100644 index 883e515..0000000 --- a/components/external-link.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Href, Link } from 'expo-router'; -import { openBrowserAsync, WebBrowserPresentationStyle } from 'expo-web-browser'; -import { type ComponentProps } from 'react'; - -type Props = Omit, 'href'> & { href: Href & string }; - -export function ExternalLink({ href, ...rest }: Props) { - return ( - { - if (process.env.EXPO_OS !== 'web') { - // Prevent the default behavior of linking to the default browser on native. - event.preventDefault(); - // Open the link in an in-app browser. - await openBrowserAsync(href, { - presentationStyle: WebBrowserPresentationStyle.AUTOMATIC, - }); - } - }} - /> - ); -} diff --git a/components/haptic-tab.tsx b/components/haptic-tab.tsx deleted file mode 100644 index 7f3981c..0000000 --- a/components/haptic-tab.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { BottomTabBarButtonProps } from '@react-navigation/bottom-tabs'; -import { PlatformPressable } from '@react-navigation/elements'; -import * as Haptics from 'expo-haptics'; - -export function HapticTab(props: BottomTabBarButtonProps) { - return ( - { - if (process.env.EXPO_OS === 'ios') { - // Add a soft haptic feedback when pressing down on the tabs. - Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); - } - props.onPressIn?.(ev); - }} - /> - ); -} diff --git a/components/hello-wave.tsx b/components/hello-wave.tsx deleted file mode 100644 index 5def547..0000000 --- a/components/hello-wave.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import Animated from 'react-native-reanimated'; - -export function HelloWave() { - return ( - - 👋 - - ); -} diff --git a/components/parallax-scroll-view.tsx b/components/parallax-scroll-view.tsx deleted file mode 100644 index 6f674a7..0000000 --- a/components/parallax-scroll-view.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import type { PropsWithChildren, ReactElement } from 'react'; -import { StyleSheet } from 'react-native'; -import Animated, { - interpolate, - useAnimatedRef, - useAnimatedStyle, - useScrollOffset, -} from 'react-native-reanimated'; - -import { ThemedView } from '@/components/themed-view'; -import { useColorScheme } from '@/hooks/use-color-scheme'; -import { useThemeColor } from '@/hooks/use-theme-color'; - -const HEADER_HEIGHT = 250; - -type Props = PropsWithChildren<{ - headerImage: ReactElement; - headerBackgroundColor: { dark: string; light: string }; -}>; - -export default function ParallaxScrollView({ - children, - headerImage, - headerBackgroundColor, -}: Props) { - const backgroundColor = useThemeColor({}, 'background'); - const colorScheme = useColorScheme() ?? 'light'; - const scrollRef = useAnimatedRef(); - const scrollOffset = useScrollOffset(scrollRef); - const headerAnimatedStyle = useAnimatedStyle(() => { - return { - transform: [ - { - translateY: interpolate( - scrollOffset.value, - [-HEADER_HEIGHT, 0, HEADER_HEIGHT], - [-HEADER_HEIGHT / 2, 0, HEADER_HEIGHT * 0.75] - ), - }, - { - scale: interpolate(scrollOffset.value, [-HEADER_HEIGHT, 0, HEADER_HEIGHT], [2, 1, 1]), - }, - ], - }; - }); - - return ( - - - {headerImage} - - {children} - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - }, - header: { - height: HEADER_HEIGHT, - overflow: 'hidden', - }, - content: { - flex: 1, - padding: 32, - gap: 16, - overflow: 'hidden', - }, -}); diff --git a/components/sign-in/sign-in-error.jsx b/components/sign-in/sign-in-error.jsx new file mode 100644 index 0000000..7f8f238 --- /dev/null +++ b/components/sign-in/sign-in-error.jsx @@ -0,0 +1,53 @@ +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { StyleSheet, View } from "react-native"; +import { Text } from "react-native-paper"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectSignInError } from "../../redux/user/user.selectors"; + +const mapStateToProps = createStructuredSelector({ + signInError: selectSignInError, +}); + +function SignInError({ signInError }) { + const [errorText, setErrorText] = useState(""); + const { t } = useTranslation(); + + useEffect(() => { + let text; + if (signInError && signInError.code) { + switch (signInError.code) { + case "auth/user-not-found": + text = t("signin.errors.wronginfo"); + break; + case "auth/invalid-email": + text = t("signin.errors.wronginfo"); + break; + case "auth/wrong-password": + text = t("signin.errors.wronginfo"); + break; + default: + text = signInError.code + " " + signInError.message; + break; + } + setErrorText(text); + } + }, [t, signInError, setErrorText]); + return ( + + {errorText ? {errorText} : null} + + ); +} + +export default connect(mapStateToProps, null)(SignInError); + +const localStyles = StyleSheet.create({ + alert: { + color: "red", + textAlign: "center", + margin: 15, + padding: 15, + }, +}); diff --git a/components/sign-in/sign-in.jsx b/components/sign-in/sign-in.jsx new file mode 100644 index 0000000..5512b80 --- /dev/null +++ b/components/sign-in/sign-in.jsx @@ -0,0 +1,132 @@ +import { Formik } from "formik"; +import React from "react"; +import { useTranslation } from "react-i18next"; +import { Image, StyleSheet, View } from "react-native"; +import { Button, Text, TextInput } from "react-native-paper"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { emailSignInStart } from "../../redux/user/user.actions"; +import SignInError from "./sign-in-error"; + +import { + selectCurrentUser, + selectSigningIn, +} from "../../redux/user/user.selectors"; +//import SignInErrorAlertComponent from "../sign-in-error-alert/sign-in-error-alert.component"; +import Constants from "expo-constants"; + +const mapStateToProps = createStructuredSelector({ + currentUser: selectCurrentUser, + signingIn: selectSigningIn, +}); + +const mapDispatchToProps = (dispatch) => ({ + emailSignInStart: (email, password) => + dispatch(emailSignInStart({ email, password })), +}); + +export function SignIn({ emailSignInStart, signingIn }) { + const { t } = useTranslation(); + + const formSubmit = (values) => { + const { email, password } = values; + emailSignInStart(email, password); + }; + + return ( + + + + {t("app.title")} + + + + {({ handleChange, handleBlur, handleSubmit, values }) => ( + + + + + + + + + )} + + + + {t("settings.labels.version", { + number: Constants.expoConfig.version, + })} + + + ); +} +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: "center", + alignItems: "center", + display: "flex", + }, + imageContainer: { + display: "flex", + marginTop: 80, + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + gap: 10, + }, + logo: { + maxWidth: "20%", + resizeMode: "contain", + }, + formContainer: { + display: "flex", + width: "100%", + padding: 20, + gap: 10, + }, + // content: { + // display: "flex", + // flex: 1, + // }, + // logo: { width: 100, height: 100 }, + inputContainer: { + marginBottom: 20, + display: "flex", + }, + input: {}, + footer: { + padding: 10, + alignSelf: "center", + }, +}); + +export default connect(mapStateToProps, mapDispatchToProps)(SignIn); diff --git a/components/themed-text.tsx b/components/themed-text.tsx deleted file mode 100644 index d79d0a1..0000000 --- a/components/themed-text.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { StyleSheet, Text, type TextProps } from 'react-native'; - -import { useThemeColor } from '@/hooks/use-theme-color'; - -export type ThemedTextProps = TextProps & { - lightColor?: string; - darkColor?: string; - type?: 'default' | 'title' | 'defaultSemiBold' | 'subtitle' | 'link'; -}; - -export function ThemedText({ - style, - lightColor, - darkColor, - type = 'default', - ...rest -}: ThemedTextProps) { - const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text'); - - return ( - - ); -} - -const styles = StyleSheet.create({ - default: { - fontSize: 16, - lineHeight: 24, - }, - defaultSemiBold: { - fontSize: 16, - lineHeight: 24, - fontWeight: '600', - }, - title: { - fontSize: 32, - fontWeight: 'bold', - lineHeight: 32, - }, - subtitle: { - fontSize: 20, - fontWeight: 'bold', - }, - link: { - lineHeight: 30, - fontSize: 16, - color: '#0a7ea4', - }, -}); diff --git a/components/themed-view.tsx b/components/themed-view.tsx deleted file mode 100644 index 6f181d8..0000000 --- a/components/themed-view.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { View, type ViewProps } from 'react-native'; - -import { useThemeColor } from '@/hooks/use-theme-color'; - -export type ThemedViewProps = ViewProps & { - lightColor?: string; - darkColor?: string; -}; - -export function ThemedView({ style, lightColor, darkColor, ...otherProps }: ThemedViewProps) { - const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background'); - - return ; -} diff --git a/components/ui/collapsible.tsx b/components/ui/collapsible.tsx deleted file mode 100644 index 6345fde..0000000 --- a/components/ui/collapsible.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { PropsWithChildren, useState } from 'react'; -import { StyleSheet, TouchableOpacity } from 'react-native'; - -import { ThemedText } from '@/components/themed-text'; -import { ThemedView } from '@/components/themed-view'; -import { IconSymbol } from '@/components/ui/icon-symbol'; -import { Colors } from '@/constants/theme'; -import { useColorScheme } from '@/hooks/use-color-scheme'; - -export function Collapsible({ children, title }: PropsWithChildren & { title: string }) { - const [isOpen, setIsOpen] = useState(false); - const theme = useColorScheme() ?? 'light'; - - return ( - - setIsOpen((value) => !value)} - activeOpacity={0.8}> - - - {title} - - {isOpen && {children}} - - ); -} - -const styles = StyleSheet.create({ - heading: { - flexDirection: 'row', - alignItems: 'center', - gap: 6, - }, - content: { - marginTop: 6, - marginLeft: 24, - }, -}); diff --git a/components/ui/icon-symbol.ios.tsx b/components/ui/icon-symbol.ios.tsx deleted file mode 100644 index 9177f4d..0000000 --- a/components/ui/icon-symbol.ios.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { SymbolView, SymbolViewProps, SymbolWeight } from 'expo-symbols'; -import { StyleProp, ViewStyle } from 'react-native'; - -export function IconSymbol({ - name, - size = 24, - color, - style, - weight = 'regular', -}: { - name: SymbolViewProps['name']; - size?: number; - color: string; - style?: StyleProp; - weight?: SymbolWeight; -}) { - return ( - - ); -} diff --git a/components/ui/icon-symbol.tsx b/components/ui/icon-symbol.tsx deleted file mode 100644 index b7ece6b..0000000 --- a/components/ui/icon-symbol.tsx +++ /dev/null @@ -1,41 +0,0 @@ -// Fallback for using MaterialIcons on Android and web. - -import MaterialIcons from '@expo/vector-icons/MaterialIcons'; -import { SymbolWeight, SymbolViewProps } from 'expo-symbols'; -import { ComponentProps } from 'react'; -import { OpaqueColorValue, type StyleProp, type TextStyle } from 'react-native'; - -type IconMapping = Record['name']>; -type IconSymbolName = keyof typeof MAPPING; - -/** - * Add your SF Symbols to Material Icons mappings here. - * - see Material Icons in the [Icons Directory](https://icons.expo.fyi). - * - see SF Symbols in the [SF Symbols](https://developer.apple.com/sf-symbols/) app. - */ -const MAPPING = { - 'house.fill': 'home', - 'paperplane.fill': 'send', - 'chevron.left.forwardslash.chevron.right': 'code', - 'chevron.right': 'chevron-right', -} as IconMapping; - -/** - * An icon component that uses native SF Symbols on iOS, and Material Icons on Android and web. - * This ensures a consistent look across platforms, and optimal resource usage. - * Icon `name`s are based on SF Symbols and require manual mapping to Material Icons. - */ -export function IconSymbol({ - name, - size = 24, - color, - style, -}: { - name: IconSymbolName; - size?: number; - color: string | OpaqueColorValue; - style?: StyleProp; - weight?: SymbolWeight; -}) { - return ; -} diff --git a/package-lock.json b/package-lock.json index f845eef..b98b6e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@apollo/client": "^3.12.11", "@expo/vector-icons": "^15.0.2", "@react-native-async-storage/async-storage": "2.2.0", + "@react-native-vector-icons/material-design-icons": "^12.3.0", "@react-navigation/bottom-tabs": "^7.4.0", "@react-navigation/elements": "^2.6.3", "@react-navigation/native": "^7.1.8", @@ -53,6 +54,7 @@ "react-native": "0.81.4", "react-native-gesture-handler": "~2.28.0", "react-native-image-viewing": "^0.2.2", + "react-native-paper": "^5.14.5", "react-native-reanimated": "~4.1.1", "react-native-safe-area-context": "~5.6.0", "react-native-screens": "~4.16.0", @@ -1606,6 +1608,28 @@ "node": ">=6.9.0" } }, + "node_modules/@callstack/react-theme-provider": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@callstack/react-theme-provider/-/react-theme-provider-3.0.9.tgz", + "integrity": "sha512-tTQ0uDSCL0ypeMa8T/E9wAZRGKWj8kXP7+6RYgPTfOPs9N07C9xM8P02GJ3feETap4Ux5S69D9nteq9mEj86NA==", + "license": "MIT", + "dependencies": { + "deepmerge": "^3.2.0", + "hoist-non-react-statics": "^3.3.0" + }, + "peerDependencies": { + "react": ">=16.3.0" + } + }, + "node_modules/@callstack/react-theme-provider/node_modules/deepmerge": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.3.0.tgz", + "integrity": "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@egjs/hammerjs": { "version": "2.0.17", "resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz", @@ -4317,6 +4341,132 @@ "react-native": "^0.0.0-0 || >=0.65 <1.0" } }, + "node_modules/@react-native-vector-icons/common": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@react-native-vector-icons/common/-/common-12.3.0.tgz", + "integrity": "sha512-5GMBcLBkA0MuciweYcrSyvi9fYGanfVnE2J+pwHx1QiaVgTaoCm4rylJgSS77MVI5qUiGh7aJpqq5afSz2U4bw==", + "license": "MIT", + "dependencies": { + "find-up": "^7.0.0", + "picocolors": "^1.1.1", + "plist": "^3.1.0" + }, + "bin": { + "rnvi-update-plist": "lib/commonjs/scripts/updatePlist.js" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "@react-native-vector-icons/get-image": "^12.2.0", + "react": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "@react-native-vector-icons/get-image": { + "optional": true + } + } + }, + "node_modules/@react-native-vector-icons/common/node_modules/find-up": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-7.0.0.tgz", + "integrity": "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==", + "license": "MIT", + "dependencies": { + "locate-path": "^7.2.0", + "path-exists": "^5.0.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-vector-icons/common/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "license": "MIT", + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-vector-icons/common/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-vector-icons/common/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "license": "MIT", + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-vector-icons/common/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/@react-native-vector-icons/common/node_modules/yocto-queue": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", + "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-vector-icons/material-design-icons": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@react-native-vector-icons/material-design-icons/-/material-design-icons-12.3.0.tgz", + "integrity": "sha512-0fut9zjUJtGWwjGQ0lbirmPnjMkou9vkBY3d3ZsaHqXCBgV3fGeOWuRZ17eDpsGy/9BTRtBRI85RYdFGhdcB4Q==", + "license": "MIT", + "dependencies": { + "@react-native-vector-icons/common": "^12.3.0" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/@react-native/assets-registry": { "version": "0.81.4", "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.81.4.tgz", @@ -4911,7 +5061,6 @@ "version": "19.1.17", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.17.tgz", "integrity": "sha512-Qec1E3mhALmaspIrhWt9jkQMNdw6bReVu64mjvhbhq2NFPftLPVr+l1SZgmw/66WwBNpDh7ao5AT6gF5v41PFA==", - "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -6843,7 +6992,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, "license": "MIT" }, "node_modules/data-view-buffer": { @@ -12678,6 +12826,62 @@ "react-native": "*" } }, + "node_modules/react-native-pager-view": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/react-native-pager-view/-/react-native-pager-view-6.9.1.tgz", + "integrity": "sha512-uUT0MMMbNtoSbxe9pRvdJJKEi9snjuJ3fXlZhG8F2vVMOBJVt/AFtqMPUHu9yMflmqOr08PewKzj9EPl/Yj+Gw==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-paper": { + "version": "5.14.5", + "resolved": "https://registry.npmjs.org/react-native-paper/-/react-native-paper-5.14.5.tgz", + "integrity": "sha512-eaIH5bUQjJ/mYm4AkI6caaiyc7BcHDwX6CqNDi6RIxfxfWxROsHpll1oBuwn/cFvknvA8uEAkqLk/vzVihI3AQ==", + "license": "MIT", + "workspaces": [ + "example", + "docs" + ], + "dependencies": { + "@callstack/react-theme-provider": "^3.0.9", + "color": "^3.1.2", + "use-latest-callback": "^0.2.3" + }, + "peerDependencies": { + "react": "*", + "react-native": "*", + "react-native-safe-area-context": "*" + } + }, + "node_modules/react-native-paper/node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/react-native-paper/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/react-native-paper/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, "node_modules/react-native-reanimated": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.1.2.tgz", @@ -14671,7 +14875,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -14779,6 +14983,18 @@ "node": ">=4" } }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unique-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", diff --git a/package.json b/package.json index 0da71c7..764c727 100644 --- a/package.json +++ b/package.json @@ -20,44 +20,36 @@ "lint": "expo lint" }, "dependencies": { + "@apollo/client": "^3.12.11", "@expo/vector-icons": "^15.0.2", + "@react-native-async-storage/async-storage": "2.2.0", + "@react-native-vector-icons/material-design-icons": "^12.3.0", "@react-navigation/bottom-tabs": "^7.4.0", "@react-navigation/elements": "^2.6.3", "@react-navigation/native": "^7.1.8", + "@reduxjs/toolkit": "^2.9.0", + "axios": "^1.12.2", + "dinero.js": "^1.9.1", "expo": "~54.0.12", + "expo-application": "~7.0.7", "expo-constants": "~18.0.9", + "expo-dev-client": "~6.0.13", + "expo-file-system": "~19.0.16", "expo-font": "~14.0.8", "expo-haptics": "~15.0.7", "expo-image": "~3.0.8", + "expo-image-picker": "~17.0.8", "expo-linking": "~8.0.8", + "expo-localization": "~17.0.7", + "expo-media-library": "~18.2.0", + "expo-notifications": "~0.32.12", "expo-router": "~6.0.10", "expo-splash-screen": "~31.0.10", "expo-status-bar": "~3.0.8", "expo-symbols": "~1.0.7", "expo-system-ui": "~6.0.7", - "expo-web-browser": "~15.0.8", - "react": "19.1.0", - "react-dom": "19.1.0", - "react-native": "0.81.4", - "react-native-gesture-handler": "~2.28.0", - "react-native-worklets": "0.5.1", - "react-native-reanimated": "~4.1.1", - "react-native-safe-area-context": "~5.6.0", - "react-native-screens": "~4.16.0", - "react-native-web": "~0.21.0", - "expo-application": "~7.0.7", - "@apollo/client": "^3.12.11", - "@react-native-async-storage/async-storage": "2.2.0", - "@reduxjs/toolkit": "^2.9.0", - "dinero.js": "^1.9.1", - "axios": "^1.12.2", - "expo-dev-client": "~6.0.13", - "expo-file-system": "~19.0.16", - "expo-image-picker": "~17.0.8", - "expo-localization": "~17.0.7", - "expo-media-library": "~18.2.0", - "expo-notifications": "~0.32.12", "expo-updates": "~29.0.12", + "expo-web-browser": "~15.0.8", "firebase": "^12.3.0", "formik": "^2.4.6", "graphql": "^16.11.0", @@ -68,9 +60,19 @@ "mime": "^4.1.0", "moment": "^2.30.1", "normalize-url": "^8.1.0", + "react": "19.1.0", + "react-dom": "19.1.0", "react-i18next": "^16.0.0", + "react-native": "0.81.4", + "react-native-gesture-handler": "~2.28.0", "react-native-image-viewing": "^0.2.2", + "react-native-paper": "^5.14.5", + "react-native-reanimated": "~4.1.1", + "react-native-safe-area-context": "~5.6.0", + "react-native-screens": "~4.16.0", "react-native-tab-view": "4.1.3", + "react-native-web": "~0.21.0", + "react-native-worklets": "0.5.1", "react-redux": "^9.2.0", "redux": "^5.0.1", "redux-logger": "^3.0.6", @@ -80,8 +82,8 @@ }, "devDependencies": { "@types/react": "~19.1.0", - "typescript": "~5.9.2", "eslint": "^9.25.0", - "eslint-config-expo": "~10.0.0" + "eslint-config-expo": "~10.0.0", + "typescript": "~5.9.2" } } diff --git a/translations/en-US/common.json b/translations/en-US/common.json index 153ab06..610fb74 100644 --- a/translations/en-US/common.json +++ b/translations/en-US/common.json @@ -1,310 +1,311 @@ { - "translation": { - "app": { - "nomobileaccess": "Your shop does not currently have access to ImEX Mobile. ", - "title": "ImEX Mobile" - }, - "camera": { - "titles": { - "cameratab": "Camera" - } - }, - "general": { - "actions": { - "signout": "Sign Out" - }, - "labels": { - "na": "N/A" - } - }, - "jobdetail": { - "labels": { - "claiminformation": "Claim Information", - "dates": "Dates", - "documents": "Docs", - "employeeassignments": "Employee Assignments", - "job": "Job", - "jobinfo": "Job Information", - "lines": "Lines", - "lines_desc": "Desc.", - "lines_lb_hrs": "Hrs", - "lines_lbr_ty": "Lbr. Ty.", - "lines_part_type": "Part Ty.", - "lines_price": "$", - "lines_qty": "Qty.", - "nojobnotes": "There are no notes.", - "notes": "Notes" - }, - "lbr_types": { - "LA1": "LA1", - "LA2": "LA2", - "LA3": "LA3", - "LA4": "LA4", - "LAA": "Aluminum", - "LAB": "Body", - "LAD": "Diagnostic", - "LAE": "Electrical", - "LAF": "Frame", - "LAG": "Glass", - "LAM": "Mechanical", - "LAR": "Refinish", - "LAS": "Structural", - "LAU": "User Defined" - }, - "part_types": { - "CCC": "CC Cleaning", - "CCD": "CC Damage Waiver", - "CCDR": "CC Daily Rate", - "CCF": "CC Refuel", - "CCM": "CC Mileage", - "PAA": "Aftermarket", - "PAC": "Rechromed", - "PAE": "Existing", - "PAL": "LKQ", - "PAM": "Remanufactured", - "PAN": "New/OEM", - "PAO": "Other", - "PAP": "OEM Partial", - "PAR": "Recored", - "PAS": "Sublet", - "PASL": "Sublet" - } - }, - "joblist": { - "actions": { - "refresh": "Refresh", - "swipecamera": "Add Pictures/Video" - }, - "labels": { - "activejobs": "Jobs", - "detail": "Job Detail", - "nojobs": "There are no active jobs.", - "search": "Search..." - }, - "titles": { - "jobtab": "Jobs" - } - }, - "mediabrowser": { - "actions": { - "refresh": "Refresh", - "upload": "Upload" - }, - "labels": { - "converting": "Converting", - "deleteafterupload": "Delete After Upload", - "localserver": "Local Server URL: {{url}}", - "nomedia": "Look's like there's no media on your device. Take some photos or videos and they will appear here.", - "selectjob": "--- Select a job ---", - "selectjobassetselector": "Please select a job to upload media. ", - "storageexceeded": "Unable to uploaded selected files because there is not sufficient space available on this job.", - "storageexceeded_title": "Unable to upload file(s)", - "storageused": "Storage Used: {{used}} / {{total}} ({{percent}}%)", - "temporarystorage": "* Temporary Storage *", - "uploading": "Uploading" - }, - "titles": { - "mediabrowsertab": "Media Browser" - } - }, - "mediacache": { - "actions": { - "deleteall": "Delete All", - "uploadall": "Upload All" - }, - "titles": { - "mediacachetab": "Media" - } - }, - "messaging": { - "titles": { - "messagingtab": "Messaging" - } - }, - "more": { - "titles": { - "moretab": "More" - } - }, - "objects": { - "jobs": { - "fields": { - "actual_completion": "Actual Completion", - "actual_delivery": "Actual Delivery", - "actual_in": "Actual In", - "adjustment_bottom_line": "Adjustments", - "ca_gst_registrant": "GST Registrant", - "category": "Category", - "ccc": "CC Cleaning", - "ccd": "CC Damage Waiver", - "ccdr": "CC Daily Rate", - "ccf": "CC Refuel", - "ccm": "CC Mileage", - "cieca_id": "CIECA ID", - "claim_total": "Claim Total", - "class": "Class", - "clm_no": "Claim #", - "clm_total": "Claim Total", - "csr": "Customer Service Rep.", - "customerowing": "Customer Owing", - "date_closed": "Closed", - "date_estimated": "Date Estimated", - "date_exported": "Exported", - "date_invoiced": "Invoiced", - "date_open": "Open", - "date_scheduled": "Scheduled", - "ded_amt": "Deductible", - "ded_status": "Deductible Status", - "depreciation_taxes": "Depreciation/Taxes", - "employee_body": "Body", - "employee_csr": "CSR", - "employee_prep": "Prep", - "employee_refinish": "Refinish", - "est_addr1": "Appraiser Address", - "est_co_nm": "Appraiser", - "est_ct_fn": "Appraiser First Name", - "est_ct_ln": "Appraiser Last Name", - "est_ea": "Appraiser Email", - "est_number": "Estimate #", - "est_ph1": "Appraiser Phone #", - "federal_tax_payable": "Federal Tax Payable", - "federal_tax_rate": "Federal Tax Rate", - "ins_addr1": "Insurance Co. Address", - "ins_city": "Insurance City", - "ins_co_id": "Insurance Co. ID", - "ins_co_nm": "Insurance Company Name", - "ins_ct_fn": "File Handler First Name", - "ins_ct_ln": "File Handler Last Name", - "ins_ea": "File Handler Email", - "ins_ph1": "File Handler Phone #", - "intake": { - "label": "Label", - "name": "Name", - "required": "Required?", - "type": "Type" - }, - "kmin": "Mileage In", - "kmout": "Mileage Out", - "la1": "LA1", - "la2": "LA2", - "la3": "LA3", - "la4": "LA4", - "laa": "Aluminum ", - "lab": "Body", - "labor_rate_desc": "Labor Rate Name", - "lad": "Diagnostic", - "lae": "Electrical", - "laf": "Frame", - "lag": "Glass", - "lam": "Mechanical", - "lar": "Refinish", - "las": "Structural", - "lau": "LAU", - "local_tax_rate": "Local Tax Rate", - "loss_date": "Loss Date", - "loss_desc": "Loss Description", - "ma2s": "2 Stage Paint", - "ma3s": "3 Stage Pain", - "mabl": "MABL?", - "macs": "MACS?", - "mahw": "Hazardous Waste", - "mapa": "Paint Materials", - "mash": "Shop Materials", - "matd": "Tire Disposal", - "other_amount_payable": "Other Amount Payable", - "owner": "Owner", - "owner_owing": "Cust. Owes", - "ownr_ea": "Email", - "ownr_ph1": "Phone 1", - "paa": "Aftermarket", - "pae": "Existing", - "pal": "LKQ", - "pam": "Remanufactured", - "pan": "OEM/New", - "pao": "Other", - "pap": "EOM Partial", - "par": "Re-cored", - "pas": "Sublet", - "pay_date": "Pay Date", - "phoneshort": "PH", - "policy_no": "Policy #", - "ponumber": "PO Number", - "rate_la1": "LA1", - "rate_la2": "LA2", - "rate_la3": "LA3", - "rate_la4": "LA4", - "rate_laa": "Aluminum", - "rate_lab": "Body", - "rate_lad": "Diagnostic", - "rate_lae": "Electrical", - "rate_laf": "Frame", - "rate_lag": "Glass", - "rate_lam": "Mechanical", - "rate_lar": "Refinish", - "rate_las": "Sublet", - "rate_lau": "Aluminum", - "rate_ma2s": "2 Stage Paint", - "rate_ma3s": "3 Stage Paint", - "rate_mabl": "MABL??", - "rate_macs": "MACS??", - "rate_mahw": "Hazardous Waste", - "rate_mapa": "Paint Materials", - "rate_mash": "Shop Material", - "rate_matd": "Tire Disposal", - "referralsource": "Referral Source", - "regie_number": "Registration #", - "repairtotal": "Repair Total", - "ro_number": "RO #", - "scheduled_completion": "Scheduled Completion", - "scheduled_delivery": "Scheduled Delivery", - "scheduled_in": "Scheduled In", - "selling_dealer": "Selling Dealer", - "selling_dealer_contact": "Selling Dealer Contact", - "servicecar": "Service Car", - "servicing_dealer": "Servicing Dealer", - "servicing_dealer_contact": "Servicing Dealer Contact", - "specialcoveragepolicy": "Special Coverage Policy", - "state_tax_rate": "State Tax Rate", - "status": "Job Status", - "storage_payable": "Storage/PVRT", - "tax_registration_number": "Tax Registration Number", - "towing_payable": "Towing Payable", - "unitnumber": "Unit #", - "updated_at": "Updated At", - "uploaded_by": "Uploaded By", - "vehicle": "Vehicle" - }, - "labels": { - "inproduction": "In Production" - } - } - }, - "production": { - "titles": { - "production": "Production" - } - }, - "settings": { - "labels": { - "version": "Version {{number}}" - }, - "titles": { - "settings": "Settings" - } - }, - "signin": { - "actions": { - "signin": "Sign In" - }, - "errors": { - "emailformat": "The email you have entered is not formatted correctly. ", - "usernotfound": "No user found.", - "wrongpassword": "The password you entered is not correct." - }, - "fields": { - "email": "Email", - "password": "Password" - } - } - } + "translation": { + "app": { + "nomobileaccess": "Your shop does not currently have access to ImEX Mobile. ", + "title": "ImEX Mobile" + }, + "camera": { + "titles": { + "cameratab": "Camera" + } + }, + "general": { + "actions": { + "signout": "Sign Out" + }, + "labels": { + "na": "N/A" + } + }, + "jobdetail": { + "labels": { + "claiminformation": "Claim Information", + "dates": "Dates", + "documents": "Docs", + "employeeassignments": "Employee Assignments", + "job": "Job", + "jobinfo": "Job Information", + "lines": "Lines", + "lines_desc": "Desc.", + "lines_lb_hrs": "Hrs", + "lines_lbr_ty": "Lbr. Ty.", + "lines_part_type": "Part Ty.", + "lines_price": "$", + "lines_qty": "Qty.", + "nojobnotes": "There are no notes.", + "notes": "Notes" + }, + "lbr_types": { + "LA1": "LA1", + "LA2": "LA2", + "LA3": "LA3", + "LA4": "LA4", + "LAA": "Aluminum", + "LAB": "Body", + "LAD": "Diagnostic", + "LAE": "Electrical", + "LAF": "Frame", + "LAG": "Glass", + "LAM": "Mechanical", + "LAR": "Refinish", + "LAS": "Structural", + "LAU": "User Defined" + }, + "part_types": { + "CCC": "CC Cleaning", + "CCD": "CC Damage Waiver", + "CCDR": "CC Daily Rate", + "CCF": "CC Refuel", + "CCM": "CC Mileage", + "PAA": "Aftermarket", + "PAC": "Rechromed", + "PAE": "Existing", + "PAL": "LKQ", + "PAM": "Remanufactured", + "PAN": "New/OEM", + "PAO": "Other", + "PAP": "OEM Partial", + "PAR": "Recored", + "PAS": "Sublet", + "PASL": "Sublet" + } + }, + "joblist": { + "actions": { + "refresh": "Refresh", + "swipecamera": "Add Pictures/Video" + }, + "labels": { + "activejobs": "Jobs", + "detail": "Job Detail", + "nojobs": "There are no active jobs.", + "search": "Search..." + }, + "titles": { + "jobtab": "Jobs" + } + }, + "mediabrowser": { + "actions": { + "refresh": "Refresh", + "upload": "Upload" + }, + "labels": { + "converting": "Converting", + "deleteafterupload": "Delete After Upload", + "localserver": "Local Server URL: {{url}}", + "nomedia": "Look's like there's no media on your device. Take some photos or videos and they will appear here.", + "selectjob": "--- Select a job ---", + "selectjobassetselector": "Please select a job to upload media. ", + "storageexceeded": "Unable to uploaded selected files because there is not sufficient space available on this job.", + "storageexceeded_title": "Unable to upload file(s)", + "storageused": "Storage Used: {{used}} / {{total}} ({{percent}}%)", + "temporarystorage": "* Temporary Storage *", + "uploading": "Uploading" + }, + "titles": { + "mediabrowsertab": "Media Browser" + } + }, + "mediacache": { + "actions": { + "deleteall": "Delete All", + "uploadall": "Upload All" + }, + "titles": { + "mediacachetab": "Media" + } + }, + "messaging": { + "titles": { + "messagingtab": "Messaging" + } + }, + "more": { + "titles": { + "moretab": "More" + } + }, + "objects": { + "jobs": { + "fields": { + "actual_completion": "Actual Completion", + "actual_delivery": "Actual Delivery", + "actual_in": "Actual In", + "adjustment_bottom_line": "Adjustments", + "ca_gst_registrant": "GST Registrant", + "category": "Category", + "ccc": "CC Cleaning", + "ccd": "CC Damage Waiver", + "ccdr": "CC Daily Rate", + "ccf": "CC Refuel", + "ccm": "CC Mileage", + "cieca_id": "CIECA ID", + "claim_total": "Claim Total", + "class": "Class", + "clm_no": "Claim #", + "clm_total": "Claim Total", + "csr": "Customer Service Rep.", + "customerowing": "Customer Owing", + "date_closed": "Closed", + "date_estimated": "Date Estimated", + "date_exported": "Exported", + "date_invoiced": "Invoiced", + "date_open": "Open", + "date_scheduled": "Scheduled", + "ded_amt": "Deductible", + "ded_status": "Deductible Status", + "depreciation_taxes": "Depreciation/Taxes", + "employee_body": "Body", + "employee_csr": "CSR", + "employee_prep": "Prep", + "employee_refinish": "Refinish", + "est_addr1": "Appraiser Address", + "est_co_nm": "Appraiser", + "est_ct_fn": "Appraiser First Name", + "est_ct_ln": "Appraiser Last Name", + "est_ea": "Appraiser Email", + "est_number": "Estimate #", + "est_ph1": "Appraiser Phone #", + "federal_tax_payable": "Federal Tax Payable", + "federal_tax_rate": "Federal Tax Rate", + "ins_addr1": "Insurance Co. Address", + "ins_city": "Insurance City", + "ins_co_id": "Insurance Co. ID", + "ins_co_nm": "Insurance Company Name", + "ins_ct_fn": "File Handler First Name", + "ins_ct_ln": "File Handler Last Name", + "ins_ea": "File Handler Email", + "ins_ph1": "File Handler Phone #", + "intake": { + "label": "Label", + "name": "Name", + "required": "Required?", + "type": "Type" + }, + "kmin": "Mileage In", + "kmout": "Mileage Out", + "la1": "LA1", + "la2": "LA2", + "la3": "LA3", + "la4": "LA4", + "laa": "Aluminum ", + "lab": "Body", + "labor_rate_desc": "Labor Rate Name", + "lad": "Diagnostic", + "lae": "Electrical", + "laf": "Frame", + "lag": "Glass", + "lam": "Mechanical", + "lar": "Refinish", + "las": "Structural", + "lau": "LAU", + "local_tax_rate": "Local Tax Rate", + "loss_date": "Loss Date", + "loss_desc": "Loss Description", + "ma2s": "2 Stage Paint", + "ma3s": "3 Stage Pain", + "mabl": "MABL?", + "macs": "MACS?", + "mahw": "Hazardous Waste", + "mapa": "Paint Materials", + "mash": "Shop Materials", + "matd": "Tire Disposal", + "other_amount_payable": "Other Amount Payable", + "owner": "Owner", + "owner_owing": "Cust. Owes", + "ownr_ea": "Email", + "ownr_ph1": "Phone 1", + "paa": "Aftermarket", + "pae": "Existing", + "pal": "LKQ", + "pam": "Remanufactured", + "pan": "OEM/New", + "pao": "Other", + "pap": "EOM Partial", + "par": "Re-cored", + "pas": "Sublet", + "pay_date": "Pay Date", + "phoneshort": "PH", + "policy_no": "Policy #", + "ponumber": "PO Number", + "rate_la1": "LA1", + "rate_la2": "LA2", + "rate_la3": "LA3", + "rate_la4": "LA4", + "rate_laa": "Aluminum", + "rate_lab": "Body", + "rate_lad": "Diagnostic", + "rate_lae": "Electrical", + "rate_laf": "Frame", + "rate_lag": "Glass", + "rate_lam": "Mechanical", + "rate_lar": "Refinish", + "rate_las": "Sublet", + "rate_lau": "Aluminum", + "rate_ma2s": "2 Stage Paint", + "rate_ma3s": "3 Stage Paint", + "rate_mabl": "MABL??", + "rate_macs": "MACS??", + "rate_mahw": "Hazardous Waste", + "rate_mapa": "Paint Materials", + "rate_mash": "Shop Material", + "rate_matd": "Tire Disposal", + "referralsource": "Referral Source", + "regie_number": "Registration #", + "repairtotal": "Repair Total", + "ro_number": "RO #", + "scheduled_completion": "Scheduled Completion", + "scheduled_delivery": "Scheduled Delivery", + "scheduled_in": "Scheduled In", + "selling_dealer": "Selling Dealer", + "selling_dealer_contact": "Selling Dealer Contact", + "servicecar": "Service Car", + "servicing_dealer": "Servicing Dealer", + "servicing_dealer_contact": "Servicing Dealer Contact", + "specialcoveragepolicy": "Special Coverage Policy", + "state_tax_rate": "State Tax Rate", + "status": "Job Status", + "storage_payable": "Storage/PVRT", + "tax_registration_number": "Tax Registration Number", + "towing_payable": "Towing Payable", + "unitnumber": "Unit #", + "updated_at": "Updated At", + "uploaded_by": "Uploaded By", + "vehicle": "Vehicle" + }, + "labels": { + "inproduction": "In Production" + } + } + }, + "production": { + "titles": { + "production": "Production" + } + }, + "settings": { + "labels": { + "version": "Version {{number}}" + }, + "titles": { + "settings": "Settings" + } + }, + "signin": { + "actions": { + "signin": "Sign In" + }, + "errors": { + "emailformat": "The email you have entered is not formatted correctly. ", + "usernotfound": "No user found.", + "wronginfo": "The email or password you entered is not correct.", + "wrongpassword": "The password you entered is not correct." + }, + "fields": { + "email": "Email", + "password": "Password" + } + } + } }