Clean up, reimplement native paper, and sign in screen.

This commit is contained in:
Patrick Fic
2025-10-08 08:57:14 -07:00
parent b8261f001e
commit 091606f1ca
20 changed files with 839 additions and 687 deletions

2
App.js
View File

@@ -1,6 +1,6 @@
import 'expo-dev-client';
import { ApolloProvider } from "@apollo/client"; import { ApolloProvider } from "@apollo/client";
import "expo-asset"; import "expo-asset";
import 'expo-dev-client';
import "intl"; import "intl";
import "intl/locale-data/jsonp/en"; import "intl/locale-data/jsonp/en";
import { SafeAreaProvider } from "react-native-safe-area-context"; import { SafeAreaProvider } from "react-native-safe-area-context";

View File

@@ -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 { ApolloProvider } from "@apollo/client";
import { Stack } from "expo-router";
import { Icon, Label, NativeTabs } from "expo-router/unstable-native-tabs"; import { Icon, Label, NativeTabs } from "expo-router/unstable-native-tabs";
import { useEffect } from "react";
import { useTranslation } from "react-i18next"; 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 { PersistGate } from "redux-persist/integration/react";
import { createStructuredSelector } from "reselect";
import { client } from "../graphql/client"; import { client } from "../graphql/client";
import { persistor, store } from "../redux/store"; import { persistor, store } from "../redux/store";
import "../translations/i18n"; import "../translations/i18n";
export default function TabLayout() { function AuthenticatedLayout() {
const { t } = useTranslation(); const { t } = useTranslation();
return (
<NativeTabs>
<NativeTabs.Trigger name="jobs">
<Label>{t("joblist.labels.activejobs")}</Label>
<Icon sf="checklist" drawable="custom_android_drawable" />
</NativeTabs.Trigger>
<NativeTabs.Trigger name="settings">
<Icon sf="gear" drawable="custom_settings_drawable" />
<Label>{t("settings.titles.settings")}</Label>
</NativeTabs.Trigger>
</NativeTabs>
);
}
function UnauthenticatedLayout() {
return (
<Stack>
<Stack.Screen name="sign-in" options={{ headerShown: false }} />
</Stack>
);
}
function LoadingLayout() {
return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<ActivityIndicator size="large" />
</View>
);
}
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 <LoadingLayout />;
}
if (currentUser.authorized) {
return <AuthenticatedLayout />;
}
return <UnauthenticatedLayout />;
}
const ConnectedAppContent = connect(
mapStateToProps,
mapDispatchToProps
)(AppContent);
const theme = {
...MD3LightTheme,
colors: {
...MD3LightTheme.colors,
primary: "#1890ff",
accent: "tomato",
},
};
export default function AppLayout() {
return ( return (
<Provider store={store}> <Provider store={store}>
<PersistGate persistor={persistor}> <PersistGate persistor={persistor}>
<ApolloProvider client={client}> <ApolloProvider client={client}>
<NativeTabs> <PaperProvider theme={theme}>
<NativeTabs.Trigger name="jobs"> <ConnectedAppContent />
<Label>{t("joblist.labels.activejobs")}</Label> </PaperProvider>
<Icon sf="checklist" drawable="custom_android_drawable" />
</NativeTabs.Trigger>
<NativeTabs.Trigger name="settings">
<Icon sf="gear" drawable="custom_settings_drawable" />
<Label>{t("settings.titles.settings")}</Label>
</NativeTabs.Trigger>
</NativeTabs>
</ApolloProvider> </ApolloProvider>
</PersistGate> </PersistGate>
</Provider> </Provider>

View File

@@ -1,5 +1,17 @@
import { selectCurrentUser } from "@/redux/user/user.selectors";
import { Redirect } from "expo-router"; import { Redirect } from "expo-router";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
export default function Index() { const mapStateToProps = createStructuredSelector({
return <Redirect href={`/jobs`} />; currentUser: selectCurrentUser,
});
function Index({ currentUser }) {
if (currentUser.authorized) {
return <Redirect href="/" />;
}
return <Redirect href="/sign-in" />;
} }
export default connect(mapStateToProps, null)(Index);

View File

@@ -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() { export default function Tab() {
return ( return (
<View style={styles.container}> <View style={styles.container}>
<Text>Tab [Home|Settings]</Text> <Text>Tab [Home|Settings]</Text>
<SignOutButton />
</View> </View>
); );
} }
@@ -11,7 +12,7 @@ export default function Tab() {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
flex: 1, flex: 1,
justifyContent: 'center', justifyContent: "center",
alignItems: 'center', alignItems: "center",
}, },
}); });

4
app/sign-in.tsx Normal file
View File

@@ -0,0 +1,4 @@
import SignIn from "@/components/sign-in/sign-in";
export default function SignInScreen() {
return <SignIn />;
}

View File

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@@ -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<ComponentProps<typeof Link>, 'href'> & { href: Href & string };
export function ExternalLink({ href, ...rest }: Props) {
return (
<Link
target="_blank"
{...rest}
href={href}
onPress={async (event) => {
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,
});
}
}}
/>
);
}

View File

@@ -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 (
<PlatformPressable
{...props}
onPressIn={(ev) => {
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);
}}
/>
);
}

View File

@@ -1,19 +0,0 @@
import Animated from 'react-native-reanimated';
export function HelloWave() {
return (
<Animated.Text
style={{
fontSize: 28,
lineHeight: 32,
marginTop: -6,
animationName: {
'50%': { transform: [{ rotate: '25deg' }] },
},
animationIterationCount: 4,
animationDuration: '300ms',
}}>
👋
</Animated.Text>
);
}

View File

@@ -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<Animated.ScrollView>();
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 (
<Animated.ScrollView
ref={scrollRef}
style={{ backgroundColor, flex: 1 }}
scrollEventThrottle={16}>
<Animated.View
style={[
styles.header,
{ backgroundColor: headerBackgroundColor[colorScheme] },
headerAnimatedStyle,
]}>
{headerImage}
</Animated.View>
<ThemedView style={styles.content}>{children}</ThemedView>
</Animated.ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
header: {
height: HEADER_HEIGHT,
overflow: 'hidden',
},
content: {
flex: 1,
padding: 32,
gap: 16,
overflow: 'hidden',
},
});

View File

@@ -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 (
<View>
{errorText ? <Text style={localStyles.alert}>{errorText}</Text> : null}
</View>
);
}
export default connect(mapStateToProps, null)(SignInError);
const localStyles = StyleSheet.create({
alert: {
color: "red",
textAlign: "center",
margin: 15,
padding: 15,
},
});

View File

@@ -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 (
<View styles={styles.container}>
<View style={styles.imageContainer}>
<Image
style={styles.logo}
source={require("@/assets/images/logo192.png")}
/>
<Text variant="headlineLarge">{t("app.title")}</Text>
</View>
<Formik initialValues={{ email: "", password: "" }} onSubmit={formSubmit}>
{({ handleChange, handleBlur, handleSubmit, values }) => (
<View style={styles.formContainer}>
<TextInput
label={t("signin.fields.email")}
placeholder={t("signin.fields.email")}
autoCapitalize="none"
mode="outlined"
autoComplete="email"
keyboardType="email-address"
onChangeText={handleChange("email")}
onBlur={handleBlur("email")}
value={values.email}
style={[styles.input]}
/>
<TextInput
label={t("signin.fields.password")}
placeholder={t("signin.fields.password")}
secureTextEntry={true}
autoCorrect={false}
mode="outlined"
autoCapitalize="none"
onChangeText={handleChange("password")}
onBlur={handleBlur("password")}
value={values.password}
style={[styles.input]}
/>
<SignInError />
<Button loading={signingIn} onPress={handleSubmit}>
{t("signin.actions.signin")}
</Button>
</View>
)}
</Formik>
<Text style={styles.footer}>
{t("settings.labels.version", {
number: Constants.expoConfig.version,
})}
</Text>
</View>
);
}
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);

View File

@@ -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 (
<Text
style={[
{ color },
type === 'default' ? styles.default : undefined,
type === 'title' ? styles.title : undefined,
type === 'defaultSemiBold' ? styles.defaultSemiBold : undefined,
type === 'subtitle' ? styles.subtitle : undefined,
type === 'link' ? styles.link : undefined,
style,
]}
{...rest}
/>
);
}
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',
},
});

View File

@@ -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 <View style={[{ backgroundColor }, style]} {...otherProps} />;
}

View File

@@ -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 (
<ThemedView>
<TouchableOpacity
style={styles.heading}
onPress={() => setIsOpen((value) => !value)}
activeOpacity={0.8}>
<IconSymbol
name="chevron.right"
size={18}
weight="medium"
color={theme === 'light' ? Colors.light.icon : Colors.dark.icon}
style={{ transform: [{ rotate: isOpen ? '90deg' : '0deg' }] }}
/>
<ThemedText type="defaultSemiBold">{title}</ThemedText>
</TouchableOpacity>
{isOpen && <ThemedView style={styles.content}>{children}</ThemedView>}
</ThemedView>
);
}
const styles = StyleSheet.create({
heading: {
flexDirection: 'row',
alignItems: 'center',
gap: 6,
},
content: {
marginTop: 6,
marginLeft: 24,
},
});

View File

@@ -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<ViewStyle>;
weight?: SymbolWeight;
}) {
return (
<SymbolView
weight={weight}
tintColor={color}
resizeMode="scaleAspectFit"
name={name}
style={[
{
width: size,
height: size,
},
style,
]}
/>
);
}

View File

@@ -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<SymbolViewProps['name'], ComponentProps<typeof MaterialIcons>['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<TextStyle>;
weight?: SymbolWeight;
}) {
return <MaterialIcons color={color} size={size} name={MAPPING[name]} style={style} />;
}

222
package-lock.json generated
View File

@@ -11,6 +11,7 @@
"@apollo/client": "^3.12.11", "@apollo/client": "^3.12.11",
"@expo/vector-icons": "^15.0.2", "@expo/vector-icons": "^15.0.2",
"@react-native-async-storage/async-storage": "2.2.0", "@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/bottom-tabs": "^7.4.0",
"@react-navigation/elements": "^2.6.3", "@react-navigation/elements": "^2.6.3",
"@react-navigation/native": "^7.1.8", "@react-navigation/native": "^7.1.8",
@@ -53,6 +54,7 @@
"react-native": "0.81.4", "react-native": "0.81.4",
"react-native-gesture-handler": "~2.28.0", "react-native-gesture-handler": "~2.28.0",
"react-native-image-viewing": "^0.2.2", "react-native-image-viewing": "^0.2.2",
"react-native-paper": "^5.14.5",
"react-native-reanimated": "~4.1.1", "react-native-reanimated": "~4.1.1",
"react-native-safe-area-context": "~5.6.0", "react-native-safe-area-context": "~5.6.0",
"react-native-screens": "~4.16.0", "react-native-screens": "~4.16.0",
@@ -1606,6 +1608,28 @@
"node": ">=6.9.0" "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": { "node_modules/@egjs/hammerjs": {
"version": "2.0.17", "version": "2.0.17",
"resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz", "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" "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": { "node_modules/@react-native/assets-registry": {
"version": "0.81.4", "version": "0.81.4",
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.81.4.tgz", "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.81.4.tgz",
@@ -4911,7 +5061,6 @@
"version": "19.1.17", "version": "19.1.17",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.17.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.17.tgz",
"integrity": "sha512-Qec1E3mhALmaspIrhWt9jkQMNdw6bReVu64mjvhbhq2NFPftLPVr+l1SZgmw/66WwBNpDh7ao5AT6gF5v41PFA==", "integrity": "sha512-Qec1E3mhALmaspIrhWt9jkQMNdw6bReVu64mjvhbhq2NFPftLPVr+l1SZgmw/66WwBNpDh7ao5AT6gF5v41PFA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"csstype": "^3.0.2" "csstype": "^3.0.2"
@@ -6843,7 +6992,6 @@
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/data-view-buffer": { "node_modules/data-view-buffer": {
@@ -12678,6 +12826,62 @@
"react-native": "*" "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": { "node_modules/react-native-reanimated": {
"version": "4.1.2", "version": "4.1.2",
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.1.2.tgz", "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.1.2.tgz",
@@ -14671,7 +14875,7 @@
"version": "5.9.3", "version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
@@ -14779,6 +14983,18 @@
"node": ">=4" "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": { "node_modules/unique-string": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz",

View File

@@ -20,44 +20,36 @@
"lint": "expo lint" "lint": "expo lint"
}, },
"dependencies": { "dependencies": {
"@apollo/client": "^3.12.11",
"@expo/vector-icons": "^15.0.2", "@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/bottom-tabs": "^7.4.0",
"@react-navigation/elements": "^2.6.3", "@react-navigation/elements": "^2.6.3",
"@react-navigation/native": "^7.1.8", "@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": "~54.0.12",
"expo-application": "~7.0.7",
"expo-constants": "~18.0.9", "expo-constants": "~18.0.9",
"expo-dev-client": "~6.0.13",
"expo-file-system": "~19.0.16",
"expo-font": "~14.0.8", "expo-font": "~14.0.8",
"expo-haptics": "~15.0.7", "expo-haptics": "~15.0.7",
"expo-image": "~3.0.8", "expo-image": "~3.0.8",
"expo-image-picker": "~17.0.8",
"expo-linking": "~8.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-router": "~6.0.10",
"expo-splash-screen": "~31.0.10", "expo-splash-screen": "~31.0.10",
"expo-status-bar": "~3.0.8", "expo-status-bar": "~3.0.8",
"expo-symbols": "~1.0.7", "expo-symbols": "~1.0.7",
"expo-system-ui": "~6.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-updates": "~29.0.12",
"expo-web-browser": "~15.0.8",
"firebase": "^12.3.0", "firebase": "^12.3.0",
"formik": "^2.4.6", "formik": "^2.4.6",
"graphql": "^16.11.0", "graphql": "^16.11.0",
@@ -68,9 +60,19 @@
"mime": "^4.1.0", "mime": "^4.1.0",
"moment": "^2.30.1", "moment": "^2.30.1",
"normalize-url": "^8.1.0", "normalize-url": "^8.1.0",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-i18next": "^16.0.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-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-tab-view": "4.1.3",
"react-native-web": "~0.21.0",
"react-native-worklets": "0.5.1",
"react-redux": "^9.2.0", "react-redux": "^9.2.0",
"redux": "^5.0.1", "redux": "^5.0.1",
"redux-logger": "^3.0.6", "redux-logger": "^3.0.6",
@@ -80,8 +82,8 @@
}, },
"devDependencies": { "devDependencies": {
"@types/react": "~19.1.0", "@types/react": "~19.1.0",
"typescript": "~5.9.2",
"eslint": "^9.25.0", "eslint": "^9.25.0",
"eslint-config-expo": "~10.0.0" "eslint-config-expo": "~10.0.0",
"typescript": "~5.9.2"
} }
} }

View File

@@ -1,310 +1,311 @@
{ {
"translation": { "translation": {
"app": { "app": {
"nomobileaccess": "Your shop does not currently have access to ImEX Mobile. ", "nomobileaccess": "Your shop does not currently have access to ImEX Mobile. ",
"title": "ImEX Mobile" "title": "ImEX Mobile"
}, },
"camera": { "camera": {
"titles": { "titles": {
"cameratab": "Camera" "cameratab": "Camera"
} }
}, },
"general": { "general": {
"actions": { "actions": {
"signout": "Sign Out" "signout": "Sign Out"
}, },
"labels": { "labels": {
"na": "N/A" "na": "N/A"
} }
}, },
"jobdetail": { "jobdetail": {
"labels": { "labels": {
"claiminformation": "Claim Information", "claiminformation": "Claim Information",
"dates": "Dates", "dates": "Dates",
"documents": "Docs", "documents": "Docs",
"employeeassignments": "Employee Assignments", "employeeassignments": "Employee Assignments",
"job": "Job", "job": "Job",
"jobinfo": "Job Information", "jobinfo": "Job Information",
"lines": "Lines", "lines": "Lines",
"lines_desc": "Desc.", "lines_desc": "Desc.",
"lines_lb_hrs": "Hrs", "lines_lb_hrs": "Hrs",
"lines_lbr_ty": "Lbr. Ty.", "lines_lbr_ty": "Lbr. Ty.",
"lines_part_type": "Part Ty.", "lines_part_type": "Part Ty.",
"lines_price": "$", "lines_price": "$",
"lines_qty": "Qty.", "lines_qty": "Qty.",
"nojobnotes": "There are no notes.", "nojobnotes": "There are no notes.",
"notes": "Notes" "notes": "Notes"
}, },
"lbr_types": { "lbr_types": {
"LA1": "LA1", "LA1": "LA1",
"LA2": "LA2", "LA2": "LA2",
"LA3": "LA3", "LA3": "LA3",
"LA4": "LA4", "LA4": "LA4",
"LAA": "Aluminum", "LAA": "Aluminum",
"LAB": "Body", "LAB": "Body",
"LAD": "Diagnostic", "LAD": "Diagnostic",
"LAE": "Electrical", "LAE": "Electrical",
"LAF": "Frame", "LAF": "Frame",
"LAG": "Glass", "LAG": "Glass",
"LAM": "Mechanical", "LAM": "Mechanical",
"LAR": "Refinish", "LAR": "Refinish",
"LAS": "Structural", "LAS": "Structural",
"LAU": "User Defined" "LAU": "User Defined"
}, },
"part_types": { "part_types": {
"CCC": "CC Cleaning", "CCC": "CC Cleaning",
"CCD": "CC Damage Waiver", "CCD": "CC Damage Waiver",
"CCDR": "CC Daily Rate", "CCDR": "CC Daily Rate",
"CCF": "CC Refuel", "CCF": "CC Refuel",
"CCM": "CC Mileage", "CCM": "CC Mileage",
"PAA": "Aftermarket", "PAA": "Aftermarket",
"PAC": "Rechromed", "PAC": "Rechromed",
"PAE": "Existing", "PAE": "Existing",
"PAL": "LKQ", "PAL": "LKQ",
"PAM": "Remanufactured", "PAM": "Remanufactured",
"PAN": "New/OEM", "PAN": "New/OEM",
"PAO": "Other", "PAO": "Other",
"PAP": "OEM Partial", "PAP": "OEM Partial",
"PAR": "Recored", "PAR": "Recored",
"PAS": "Sublet", "PAS": "Sublet",
"PASL": "Sublet" "PASL": "Sublet"
} }
}, },
"joblist": { "joblist": {
"actions": { "actions": {
"refresh": "Refresh", "refresh": "Refresh",
"swipecamera": "Add Pictures/Video" "swipecamera": "Add Pictures/Video"
}, },
"labels": { "labels": {
"activejobs": "Jobs", "activejobs": "Jobs",
"detail": "Job Detail", "detail": "Job Detail",
"nojobs": "There are no active jobs.", "nojobs": "There are no active jobs.",
"search": "Search..." "search": "Search..."
}, },
"titles": { "titles": {
"jobtab": "Jobs" "jobtab": "Jobs"
} }
}, },
"mediabrowser": { "mediabrowser": {
"actions": { "actions": {
"refresh": "Refresh", "refresh": "Refresh",
"upload": "Upload" "upload": "Upload"
}, },
"labels": { "labels": {
"converting": "Converting", "converting": "Converting",
"deleteafterupload": "Delete After Upload", "deleteafterupload": "Delete After Upload",
"localserver": "Local Server URL: {{url}}", "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.", "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 ---", "selectjob": "--- Select a job ---",
"selectjobassetselector": "Please select a job to upload media. ", "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": "Unable to uploaded selected files because there is not sufficient space available on this job.",
"storageexceeded_title": "Unable to upload file(s)", "storageexceeded_title": "Unable to upload file(s)",
"storageused": "Storage Used: {{used}} / {{total}} ({{percent}}%)", "storageused": "Storage Used: {{used}} / {{total}} ({{percent}}%)",
"temporarystorage": "* Temporary Storage *", "temporarystorage": "* Temporary Storage *",
"uploading": "Uploading" "uploading": "Uploading"
}, },
"titles": { "titles": {
"mediabrowsertab": "Media Browser" "mediabrowsertab": "Media Browser"
} }
}, },
"mediacache": { "mediacache": {
"actions": { "actions": {
"deleteall": "Delete All", "deleteall": "Delete All",
"uploadall": "Upload All" "uploadall": "Upload All"
}, },
"titles": { "titles": {
"mediacachetab": "Media" "mediacachetab": "Media"
} }
}, },
"messaging": { "messaging": {
"titles": { "titles": {
"messagingtab": "Messaging" "messagingtab": "Messaging"
} }
}, },
"more": { "more": {
"titles": { "titles": {
"moretab": "More" "moretab": "More"
} }
}, },
"objects": { "objects": {
"jobs": { "jobs": {
"fields": { "fields": {
"actual_completion": "Actual Completion", "actual_completion": "Actual Completion",
"actual_delivery": "Actual Delivery", "actual_delivery": "Actual Delivery",
"actual_in": "Actual In", "actual_in": "Actual In",
"adjustment_bottom_line": "Adjustments", "adjustment_bottom_line": "Adjustments",
"ca_gst_registrant": "GST Registrant", "ca_gst_registrant": "GST Registrant",
"category": "Category", "category": "Category",
"ccc": "CC Cleaning", "ccc": "CC Cleaning",
"ccd": "CC Damage Waiver", "ccd": "CC Damage Waiver",
"ccdr": "CC Daily Rate", "ccdr": "CC Daily Rate",
"ccf": "CC Refuel", "ccf": "CC Refuel",
"ccm": "CC Mileage", "ccm": "CC Mileage",
"cieca_id": "CIECA ID", "cieca_id": "CIECA ID",
"claim_total": "Claim Total", "claim_total": "Claim Total",
"class": "Class", "class": "Class",
"clm_no": "Claim #", "clm_no": "Claim #",
"clm_total": "Claim Total", "clm_total": "Claim Total",
"csr": "Customer Service Rep.", "csr": "Customer Service Rep.",
"customerowing": "Customer Owing", "customerowing": "Customer Owing",
"date_closed": "Closed", "date_closed": "Closed",
"date_estimated": "Date Estimated", "date_estimated": "Date Estimated",
"date_exported": "Exported", "date_exported": "Exported",
"date_invoiced": "Invoiced", "date_invoiced": "Invoiced",
"date_open": "Open", "date_open": "Open",
"date_scheduled": "Scheduled", "date_scheduled": "Scheduled",
"ded_amt": "Deductible", "ded_amt": "Deductible",
"ded_status": "Deductible Status", "ded_status": "Deductible Status",
"depreciation_taxes": "Depreciation/Taxes", "depreciation_taxes": "Depreciation/Taxes",
"employee_body": "Body", "employee_body": "Body",
"employee_csr": "CSR", "employee_csr": "CSR",
"employee_prep": "Prep", "employee_prep": "Prep",
"employee_refinish": "Refinish", "employee_refinish": "Refinish",
"est_addr1": "Appraiser Address", "est_addr1": "Appraiser Address",
"est_co_nm": "Appraiser", "est_co_nm": "Appraiser",
"est_ct_fn": "Appraiser First Name", "est_ct_fn": "Appraiser First Name",
"est_ct_ln": "Appraiser Last Name", "est_ct_ln": "Appraiser Last Name",
"est_ea": "Appraiser Email", "est_ea": "Appraiser Email",
"est_number": "Estimate #", "est_number": "Estimate #",
"est_ph1": "Appraiser Phone #", "est_ph1": "Appraiser Phone #",
"federal_tax_payable": "Federal Tax Payable", "federal_tax_payable": "Federal Tax Payable",
"federal_tax_rate": "Federal Tax Rate", "federal_tax_rate": "Federal Tax Rate",
"ins_addr1": "Insurance Co. Address", "ins_addr1": "Insurance Co. Address",
"ins_city": "Insurance City", "ins_city": "Insurance City",
"ins_co_id": "Insurance Co. ID", "ins_co_id": "Insurance Co. ID",
"ins_co_nm": "Insurance Company Name", "ins_co_nm": "Insurance Company Name",
"ins_ct_fn": "File Handler First Name", "ins_ct_fn": "File Handler First Name",
"ins_ct_ln": "File Handler Last Name", "ins_ct_ln": "File Handler Last Name",
"ins_ea": "File Handler Email", "ins_ea": "File Handler Email",
"ins_ph1": "File Handler Phone #", "ins_ph1": "File Handler Phone #",
"intake": { "intake": {
"label": "Label", "label": "Label",
"name": "Name", "name": "Name",
"required": "Required?", "required": "Required?",
"type": "Type" "type": "Type"
}, },
"kmin": "Mileage In", "kmin": "Mileage In",
"kmout": "Mileage Out", "kmout": "Mileage Out",
"la1": "LA1", "la1": "LA1",
"la2": "LA2", "la2": "LA2",
"la3": "LA3", "la3": "LA3",
"la4": "LA4", "la4": "LA4",
"laa": "Aluminum ", "laa": "Aluminum ",
"lab": "Body", "lab": "Body",
"labor_rate_desc": "Labor Rate Name", "labor_rate_desc": "Labor Rate Name",
"lad": "Diagnostic", "lad": "Diagnostic",
"lae": "Electrical", "lae": "Electrical",
"laf": "Frame", "laf": "Frame",
"lag": "Glass", "lag": "Glass",
"lam": "Mechanical", "lam": "Mechanical",
"lar": "Refinish", "lar": "Refinish",
"las": "Structural", "las": "Structural",
"lau": "LAU", "lau": "LAU",
"local_tax_rate": "Local Tax Rate", "local_tax_rate": "Local Tax Rate",
"loss_date": "Loss Date", "loss_date": "Loss Date",
"loss_desc": "Loss Description", "loss_desc": "Loss Description",
"ma2s": "2 Stage Paint", "ma2s": "2 Stage Paint",
"ma3s": "3 Stage Pain", "ma3s": "3 Stage Pain",
"mabl": "MABL?", "mabl": "MABL?",
"macs": "MACS?", "macs": "MACS?",
"mahw": "Hazardous Waste", "mahw": "Hazardous Waste",
"mapa": "Paint Materials", "mapa": "Paint Materials",
"mash": "Shop Materials", "mash": "Shop Materials",
"matd": "Tire Disposal", "matd": "Tire Disposal",
"other_amount_payable": "Other Amount Payable", "other_amount_payable": "Other Amount Payable",
"owner": "Owner", "owner": "Owner",
"owner_owing": "Cust. Owes", "owner_owing": "Cust. Owes",
"ownr_ea": "Email", "ownr_ea": "Email",
"ownr_ph1": "Phone 1", "ownr_ph1": "Phone 1",
"paa": "Aftermarket", "paa": "Aftermarket",
"pae": "Existing", "pae": "Existing",
"pal": "LKQ", "pal": "LKQ",
"pam": "Remanufactured", "pam": "Remanufactured",
"pan": "OEM/New", "pan": "OEM/New",
"pao": "Other", "pao": "Other",
"pap": "EOM Partial", "pap": "EOM Partial",
"par": "Re-cored", "par": "Re-cored",
"pas": "Sublet", "pas": "Sublet",
"pay_date": "Pay Date", "pay_date": "Pay Date",
"phoneshort": "PH", "phoneshort": "PH",
"policy_no": "Policy #", "policy_no": "Policy #",
"ponumber": "PO Number", "ponumber": "PO Number",
"rate_la1": "LA1", "rate_la1": "LA1",
"rate_la2": "LA2", "rate_la2": "LA2",
"rate_la3": "LA3", "rate_la3": "LA3",
"rate_la4": "LA4", "rate_la4": "LA4",
"rate_laa": "Aluminum", "rate_laa": "Aluminum",
"rate_lab": "Body", "rate_lab": "Body",
"rate_lad": "Diagnostic", "rate_lad": "Diagnostic",
"rate_lae": "Electrical", "rate_lae": "Electrical",
"rate_laf": "Frame", "rate_laf": "Frame",
"rate_lag": "Glass", "rate_lag": "Glass",
"rate_lam": "Mechanical", "rate_lam": "Mechanical",
"rate_lar": "Refinish", "rate_lar": "Refinish",
"rate_las": "Sublet", "rate_las": "Sublet",
"rate_lau": "Aluminum", "rate_lau": "Aluminum",
"rate_ma2s": "2 Stage Paint", "rate_ma2s": "2 Stage Paint",
"rate_ma3s": "3 Stage Paint", "rate_ma3s": "3 Stage Paint",
"rate_mabl": "MABL??", "rate_mabl": "MABL??",
"rate_macs": "MACS??", "rate_macs": "MACS??",
"rate_mahw": "Hazardous Waste", "rate_mahw": "Hazardous Waste",
"rate_mapa": "Paint Materials", "rate_mapa": "Paint Materials",
"rate_mash": "Shop Material", "rate_mash": "Shop Material",
"rate_matd": "Tire Disposal", "rate_matd": "Tire Disposal",
"referralsource": "Referral Source", "referralsource": "Referral Source",
"regie_number": "Registration #", "regie_number": "Registration #",
"repairtotal": "Repair Total", "repairtotal": "Repair Total",
"ro_number": "RO #", "ro_number": "RO #",
"scheduled_completion": "Scheduled Completion", "scheduled_completion": "Scheduled Completion",
"scheduled_delivery": "Scheduled Delivery", "scheduled_delivery": "Scheduled Delivery",
"scheduled_in": "Scheduled In", "scheduled_in": "Scheduled In",
"selling_dealer": "Selling Dealer", "selling_dealer": "Selling Dealer",
"selling_dealer_contact": "Selling Dealer Contact", "selling_dealer_contact": "Selling Dealer Contact",
"servicecar": "Service Car", "servicecar": "Service Car",
"servicing_dealer": "Servicing Dealer", "servicing_dealer": "Servicing Dealer",
"servicing_dealer_contact": "Servicing Dealer Contact", "servicing_dealer_contact": "Servicing Dealer Contact",
"specialcoveragepolicy": "Special Coverage Policy", "specialcoveragepolicy": "Special Coverage Policy",
"state_tax_rate": "State Tax Rate", "state_tax_rate": "State Tax Rate",
"status": "Job Status", "status": "Job Status",
"storage_payable": "Storage/PVRT", "storage_payable": "Storage/PVRT",
"tax_registration_number": "Tax Registration Number", "tax_registration_number": "Tax Registration Number",
"towing_payable": "Towing Payable", "towing_payable": "Towing Payable",
"unitnumber": "Unit #", "unitnumber": "Unit #",
"updated_at": "Updated At", "updated_at": "Updated At",
"uploaded_by": "Uploaded By", "uploaded_by": "Uploaded By",
"vehicle": "Vehicle" "vehicle": "Vehicle"
}, },
"labels": { "labels": {
"inproduction": "In Production" "inproduction": "In Production"
} }
} }
}, },
"production": { "production": {
"titles": { "titles": {
"production": "Production" "production": "Production"
} }
}, },
"settings": { "settings": {
"labels": { "labels": {
"version": "Version {{number}}" "version": "Version {{number}}"
}, },
"titles": { "titles": {
"settings": "Settings" "settings": "Settings"
} }
}, },
"signin": { "signin": {
"actions": { "actions": {
"signin": "Sign In" "signin": "Sign In"
}, },
"errors": { "errors": {
"emailformat": "The email you have entered is not formatted correctly. ", "emailformat": "The email you have entered is not formatted correctly. ",
"usernotfound": "No user found.", "usernotfound": "No user found.",
"wrongpassword": "The password you entered is not correct." "wronginfo": "The email or password you entered is not correct.",
}, "wrongpassword": "The password you entered is not correct."
"fields": { },
"email": "Email", "fields": {
"password": "Password" "email": "Email",
} "password": "Password"
} }
} }
}
} }