feat(intial auth): intial auth setup for the scanner
Some checks failed
Build and Push LST Docker Image / docker (push) Has been cancelled
Some checks failed
Build and Push LST Docker Image / docker (push) Has been cancelled
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
"foregroundImage": "./assets/adaptive-icon-white.png",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"versionCode": 21,
|
||||
"versionCode": 23,
|
||||
"minSupportedVersionCode": 21,
|
||||
"predictiveBackGestureEnabled": false,
|
||||
"package": "net.alpla.lst.mobile"
|
||||
@@ -26,7 +26,7 @@
|
||||
"bundler": "metro"
|
||||
},
|
||||
"plugins": [
|
||||
"./plugins/withZebraScanner",
|
||||
"./plugins/withZebraDataWedge",
|
||||
"expo-router",
|
||||
[
|
||||
"expo-splash-screen",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"ios": "expo run:ios",
|
||||
"web": "expo start --web",
|
||||
"lint": "expo lint",
|
||||
"build:apk:clean": "expo prebuild --clean && cd android && gradlew.bat assembleRelease && npm run copy:apk",
|
||||
"build:apk:clean": "expo prebuild --clean && cd android && gradlew.bat clean && gradlew.bat assembleRelease && npm run copy:apk",
|
||||
"build:apk": "expo prebuild && cd android && gradlew.bat assembleRelease && npm run copy:apk",
|
||||
"build:mobile": "cd scripts && node runBuild.ts",
|
||||
"build:mobile:bump": "cd scripts && node runBuild.ts --bump",
|
||||
|
||||
@@ -145,34 +145,32 @@ class ZebraScannerModule(
|
||||
|
||||
Thread.sleep(500)
|
||||
|
||||
val barcodeConfig = Bundle().apply {
|
||||
putString("PLUGIN_NAME", "BARCODE")
|
||||
putString("RESET_CONFIG", "true")
|
||||
val barcodeConfig = Bundle().apply {
|
||||
putString("PLUGIN_NAME", "BARCODE")
|
||||
putString("RESET_CONFIG", "true")
|
||||
|
||||
val isLegacyTc8000 =
|
||||
android.os.Build.MODEL.contains("TC8000", ignoreCase = true)
|
||||
val props = Bundle().apply {
|
||||
putString("scanner_input_enabled", "true")
|
||||
|
||||
val props = Bundle().apply {
|
||||
putString("scanner_input_enabled", "true")
|
||||
|
||||
// Baseline that should be safe on old and new Zebra devices
|
||||
putString("scanner_selection", "auto")
|
||||
|
||||
if (!isLegacyTc8000) {
|
||||
// Newer Zebra devices
|
||||
// Auto-select internal scanner
|
||||
putString("scanner_selection", "auto")
|
||||
putString("scanner_selection_by_identifier", "AUTO")
|
||||
|
||||
// Hardware trigger behavior
|
||||
putString("hardware_trigger_enabled", "true")
|
||||
putString("trigger_mode", "2") // HARD trigger
|
||||
putString("trigger_mode", "2") // 2 = HARD trigger
|
||||
|
||||
// Disable Zebra's loud initial decode feedback
|
||||
putString("decode_audio_feedback_uri", "")
|
||||
putString("decode_haptic_feedback", "false")
|
||||
putString("decode_led_feedback", "false")
|
||||
}
|
||||
}
|
||||
|
||||
putBundle("PARAM_LIST", props)
|
||||
}
|
||||
// add in wake on trigger
|
||||
putString("trigger_wakeup_scan", "true");
|
||||
}
|
||||
|
||||
putBundle("PARAM_LIST", props)
|
||||
}
|
||||
|
||||
val intentConfig = Bundle().apply {
|
||||
putString("PLUGIN_NAME", "INTENT")
|
||||
@@ -1,13 +1,26 @@
|
||||
import React from 'react'
|
||||
import { Text, View } from 'react-native'
|
||||
import React from "react";
|
||||
import { Text, View } from "react-native";
|
||||
import { Button } from "../../components/ui/button";
|
||||
|
||||
export default function Logs() {
|
||||
return (
|
||||
<View style={{
|
||||
const getInfo = async () => {
|
||||
const info = "ho";
|
||||
|
||||
console.log(info);
|
||||
};
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
//justifyContent: "center",
|
||||
alignItems: "center",
|
||||
marginTop: 50,
|
||||
}}><Text>Logs</Text></View>
|
||||
)
|
||||
}}
|
||||
>
|
||||
<Text>Logs</Text>
|
||||
<Button onPress={getInfo}>
|
||||
<Text>Check info</Text>
|
||||
</Button>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
|
||||
import { useAppStore } from "../../hooks/useAppStore";
|
||||
import ProdScanner from "../../components/ProdScanner";
|
||||
import { View } from "react-native";
|
||||
import LSTScanner from "../../components/LSTScanner";
|
||||
import ProdScanner from "../../components/ProdScanner";
|
||||
import { useAppStore } from "../../hooks/useAppStore";
|
||||
|
||||
export default function scanner() {
|
||||
export default function Scanner() {
|
||||
const serverPort = useAppStore((s) => s.serverPort);
|
||||
return (
|
||||
<View
|
||||
@@ -16,7 +14,11 @@ export default function scanner() {
|
||||
marginTop: 50,
|
||||
}}
|
||||
>
|
||||
{parseInt(serverPort || "0", 10) >= 50000 ? <ProdScanner /> : <LSTScanner />}
|
||||
{parseInt(serverPort || "0", 10) >= 50000 ? (
|
||||
<ProdScanner />
|
||||
) : (
|
||||
<LSTScanner />
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Redirect, useRouter } from "expo-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { ActivityIndicator, Text, View } from "react-native";
|
||||
import { useAppStore } from "../hooks/useAppStore";
|
||||
import { useMobileAuthStore } from "../hooks/useMobileAuth";
|
||||
import { useServerStore } from "../hooks/useServerCheck";
|
||||
import { devDelay } from "../lib/devMode";
|
||||
|
||||
@@ -12,6 +13,7 @@ export default function Index() {
|
||||
const [message, setMessage] = useState(<Text>Starting app...</Text>);
|
||||
const [ready, setReady] = useState(false);
|
||||
const setServerVersion = useServerStore((s) => s.setServerVersion);
|
||||
//const { isUnlocked } = useMobileAuthStore();
|
||||
|
||||
const hasHydrated = useAppStore((s) => s.hasHydrated);
|
||||
const serverPort = useAppStore((s) => s.serverPort);
|
||||
@@ -86,7 +88,7 @@ export default function Index() {
|
||||
// TODO if theres an update go to update screen message :D
|
||||
setMessage(<Text>Opening LST scan app</Text>);
|
||||
await devDelay(3250);
|
||||
//router.replace("/scanner");
|
||||
|
||||
setReady(true);
|
||||
} catch (error) {
|
||||
console.log("Startup error", error);
|
||||
@@ -104,6 +106,9 @@ export default function Index() {
|
||||
setServerVersion,
|
||||
]);
|
||||
|
||||
// if (ready && !isUnlocked) {
|
||||
// return <Redirect href={"/login"} />;
|
||||
// }
|
||||
if (ready) {
|
||||
return <Redirect href="/(tabs)/scanner" />;
|
||||
}
|
||||
|
||||
52
lstMobile/src/app/login.tsx
Normal file
52
lstMobile/src/app/login.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import axios from "axios";
|
||||
import Constants from "expo-constants";
|
||||
import { useRouter } from "expo-router";
|
||||
import { useState } from "react";
|
||||
import { Alert, Button, Text, View } from "react-native";
|
||||
import { Input } from "../components/ui/input";
|
||||
import { useAppStore } from "../hooks/useAppStore";
|
||||
import { useMobileAuthStore } from "../hooks/useMobileAuth";
|
||||
|
||||
export default function Login() {
|
||||
const { setUser } = useMobileAuthStore();
|
||||
const serverPort = useAppStore((s) => s.serverPort);
|
||||
const serverIp = useAppStore((s) => s.serverIp);
|
||||
const onLogin = async () => {
|
||||
try {
|
||||
const res = await axios.get(
|
||||
`http://${serverIp}:${parseInt(serverPort || "0", 10) >= 50000 ? "3000" : serverPort}/lst/api/mobile/version`,
|
||||
{
|
||||
timeout: 5000,
|
||||
},
|
||||
);
|
||||
|
||||
console.log(res.data);
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
//justifyContent: "center",
|
||||
alignItems: "center",
|
||||
marginTop: 50,
|
||||
}}
|
||||
>
|
||||
<View className="flex items-center m-5">
|
||||
<Text style={{ fontSize: 20, fontWeight: "600" }}>
|
||||
LST Scanner Login
|
||||
</Text>
|
||||
<View className="w-64 p-4">
|
||||
<Input
|
||||
className="w-fit"
|
||||
keyboardType="number-pad"
|
||||
textContentType="oneTimeCode"
|
||||
placeholder="Pin number"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<Button title="Login" onPress={onLogin} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -154,10 +154,10 @@ export default function Setup() {
|
||||
marginBottom: 12,
|
||||
}}
|
||||
>
|
||||
<Text className="text-[12] color-#666">
|
||||
<Text className="text-sm color-[#312f2f]">
|
||||
App v{version}-{build}
|
||||
</Text>
|
||||
<Text className="text-[12] color-#666">
|
||||
<Text className="text-sm color-[#312f2f]">
|
||||
Server version - v{server?.versionName}-{server?.versionCode}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -1,24 +1,51 @@
|
||||
import React from 'react'
|
||||
import { View, Text } from 'react-native'
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { Text, View } from "react-native";
|
||||
|
||||
import { zebraScanner } from "../lib/ZebraScanner";
|
||||
|
||||
export default function LSTScanner() {
|
||||
return (
|
||||
<View><View style={{ alignItems: "center", margin: 10 }}>
|
||||
<Text style={{ fontSize: 20, fontWeight: "600" }}>LST Scanner</Text>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
marginTop: 50,
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Text>Relocate</Text>
|
||||
<Text>0 / 4</Text>
|
||||
</View>
|
||||
|
||||
{/* <View>
|
||||
const handleScan = useCallback(async (scan: any) => {
|
||||
console.log(scan);
|
||||
}, []);
|
||||
|
||||
const clearScans = () => {
|
||||
// add in
|
||||
};
|
||||
|
||||
//console.log(lastScan);
|
||||
|
||||
useEffect(() => {
|
||||
zebraScanner.ensureProfile();
|
||||
zebraScanner.startListening();
|
||||
|
||||
const sub = zebraScanner.addScanListener((scan) => {
|
||||
//console.log("SCAN:", scan);
|
||||
handleScan(scan);
|
||||
});
|
||||
|
||||
return () => {
|
||||
sub.remove();
|
||||
zebraScanner.stopListening();
|
||||
};
|
||||
}, [handleScan]);
|
||||
return (
|
||||
<View>
|
||||
<View style={{ alignItems: "center", margin: 10 }}>
|
||||
<Text style={{ fontSize: 20, fontWeight: "600" }}>LST Scanner</Text>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
marginTop: 50,
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Text>Relocate</Text>
|
||||
<Text>0 / 4</Text>
|
||||
</View>
|
||||
|
||||
{/* <View>
|
||||
<Text>List of recent scanned pallets TBA</Text>
|
||||
</View> */}
|
||||
</View>
|
||||
)
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
29
lstMobile/src/components/ui/input.tsx
Normal file
29
lstMobile/src/components/ui/input.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Platform, TextInput } from 'react-native';
|
||||
|
||||
function Input({ className, ...props }: React.ComponentProps<typeof TextInput> & React.RefAttributes<TextInput>) {
|
||||
return (
|
||||
<TextInput
|
||||
className={cn(
|
||||
'dark:bg-input/30 border-input bg-background text-foreground flex h-10 w-full min-w-0 flex-row items-center rounded-md border px-3 py-1 text-base leading-5 shadow-sm shadow-black/5 sm:h-9',
|
||||
props.editable === false &&
|
||||
cn(
|
||||
'opacity-50',
|
||||
Platform.select({ web: 'disabled:pointer-events-none disabled:cursor-not-allowed' })
|
||||
),
|
||||
Platform.select({
|
||||
web: cn(
|
||||
'placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground outline-none transition-[color,box-shadow] md:text-sm',
|
||||
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
|
||||
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive'
|
||||
),
|
||||
native: 'placeholder:text-muted-foreground/50',
|
||||
}),
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export { Input };
|
||||
28
lstMobile/src/hooks/useMobileAuth.ts
Normal file
28
lstMobile/src/hooks/useMobileAuth.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { create } from "zustand";
|
||||
|
||||
type MobileUser = {
|
||||
id: string;
|
||||
name: string;
|
||||
role: "user" | "lead" | "manager" | "admin";
|
||||
excludedCommand: string[];
|
||||
};
|
||||
|
||||
type AuthState = {
|
||||
user: MobileUser | null;
|
||||
isUnlocked: boolean;
|
||||
|
||||
setUser: (user: MobileUser) => void;
|
||||
lock: () => void;
|
||||
logout: () => void;
|
||||
};
|
||||
|
||||
export const useMobileAuthStore = create<AuthState>((set) => ({
|
||||
user: null,
|
||||
isUnlocked: false,
|
||||
|
||||
setUser: (user) => set({ user, isUnlocked: true }),
|
||||
|
||||
lock: () => set({ isUnlocked: false }),
|
||||
|
||||
logout: () => set({ user: null, isUnlocked: false }),
|
||||
}));
|
||||
@@ -28,9 +28,9 @@ export const zebraScanner = {
|
||||
ZebraScanner.triggerScan();
|
||||
},
|
||||
|
||||
ensureProfile() {
|
||||
ZebraScanner.ensureProfile();
|
||||
},
|
||||
ensureProfile() {
|
||||
ZebraScanner.ensureProfile();
|
||||
},
|
||||
|
||||
addScanListener(
|
||||
callback: (scan: ZebraScanResult) => void,
|
||||
|
||||
13
lstMobile/src/lib/auth.roleCheck.ts
Normal file
13
lstMobile/src/lib/auth.roleCheck.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
const roleRank = {
|
||||
user: 1,
|
||||
lead: 2,
|
||||
manager: 3,
|
||||
admin: 4,
|
||||
} as const;
|
||||
|
||||
export function hasMobileRole(
|
||||
userRole: keyof typeof roleRank,
|
||||
requiredRole: keyof typeof roleRank,
|
||||
) {
|
||||
return roleRank[userRole] >= roleRank[requiredRole];
|
||||
}
|
||||
Reference in New Issue
Block a user