refactor(scanner): added toasts in to make it look better
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 1m25s

This commit is contained in:
2026-05-06 19:42:52 -05:00
parent 87803eed43
commit edb3668548
8 changed files with 286 additions and 32 deletions

View File

@@ -3,10 +3,14 @@ import axios from "axios";
import { useRouter } from "expo-router"; import { useRouter } from "expo-router";
import { useState } from "react"; import { useState } from "react";
import { Alert, Button, Text, View } from "react-native"; import { Alert, Button, Text, View } from "react-native";
import Toast from "react-native-toast-message";
import { Input } from "../components/ui/input"; import { Input } from "../components/ui/input";
import { useAppStore } from "../hooks/useAppStore"; import { useAppStore } from "../hooks/useAppStore";
import { useMobileAuthStore } from "../hooks/useMobileAuth"; import { useMobileAuthStore } from "../hooks/useMobileAuth";
const formatName = (name?: string) =>
name ? name.charAt(0).toUpperCase() + name.slice(1).toLowerCase() : "";
export default function Login() { export default function Login() {
// doing this causes rerender and sub // doing this causes rerender and sub
//const { setUser } = useMobileAuthStore(); //const { setUser } = useMobileAuthStore();
@@ -33,12 +37,18 @@ export default function Login() {
if (res.status === 200) { if (res.status === 200) {
// this way to set the user is direct and basically a 1 shot // this way to set the user is direct and basically a 1 shot
Toast.show({
type: "success",
text1: `Welcome back ${formatName(res.data.data.name)}`,
});
useMobileAuthStore.getState().setUser(res.data.data); useMobileAuthStore.getState().setUser(res.data.data);
return router.replace("/(tabs)/scanner"); return router.replace("/(tabs)/scanner");
} }
} catch (error) { } catch (error) {
console.log(error); console.log(error);
Alert.alert("Login Error", `Invalid pin please try again`); //Alert.alert("Login Error", `Invalid pin please try again`);
Toast.show({ type: "error", text1: `Invalid pin please try again` });
} }
}; };

View File

@@ -2,6 +2,7 @@ import Constants from "expo-constants";
import { useRouter } from "expo-router"; import { useRouter } from "expo-router";
import { useState } from "react"; import { useState } from "react";
import { Alert, Button, Text, TextInput, View } from "react-native"; import { Alert, Button, Text, TextInput, View } from "react-native";
import Toast from "react-native-toast-message";
import { useAppStore } from "../hooks/useAppStore"; import { useAppStore } from "../hooks/useAppStore";
import { useServerStore } from "../hooks/useServerCheck"; import { useServerStore } from "../hooks/useServerCheck";
@@ -31,14 +32,23 @@ export default function Setup() {
if (pin === "6971") { if (pin === "6971") {
setAuth(true); setAuth(true);
} else { } else {
Alert.alert("Incorrect pin entered please try again"); //Alert.alert("Incorrect pin entered please try again");
Toast.show({
type: "error",
text1: "Incorrect pin entered please try again",
});
setPin(""); setPin("");
} }
}; };
const handleSave = async () => { const handleSave = async () => {
if (!serverIp.trim() || !serverPort.trim()) { if (!serverIp.trim() || !serverPort.trim()) {
Alert.alert("Missing info", "Please fill in both fields."); //Alert.alert("Missing info", "Please fill in both fields.");
Toast.show({
type: "error",
text1: "Missing info",
text2: "Please fill in both fields.",
});
return; return;
} }
@@ -50,7 +60,12 @@ export default function Setup() {
isRegistered: true, isRegistered: true,
}); });
Alert.alert("Saved", "Config saved to device."); //Alert.alert("Saved", "Config saved to device.");
Toast.show({
type: "info",
text1: "Saved",
text2: "Config saved to device.",
});
//router.replace("/"); //router.replace("/");
}; };
return ( return (

View File

@@ -1,5 +1,6 @@
import axios from "axios"; import axios from "axios";
import { format } from "date-fns-tz"; import { format } from "date-fns-tz";
import { useFocusEffect } from "expo-router";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { Alert, Button, Text, View } from "react-native"; import { Alert, Button, Text, View } from "react-native";
import { useAppStore } from "../hooks/useAppStore"; import { useAppStore } from "../hooks/useAppStore";
@@ -90,7 +91,9 @@ export default function LSTScanner() {
user: user?.name ?? "prodScan", user: user?.name ?? "prodScan",
runningNumber: scan.data.startsWith("000") runningNumber: scan.data.startsWith("000")
? parseInt(scan.data.slice(10, -1) || "000", 10).toString() ? parseInt(scan.data.slice(10, -1) || "000", 10).toString()
: "0", : scan.data.startsWith("loc")
? scan.data
: "0",
}); });
} catch (error) { } catch (error) {
console.log(error); console.log(error);
@@ -153,20 +156,21 @@ export default function LSTScanner() {
//console.log(lastScan); //console.log(lastScan);
useEffect(() => { useFocusEffect(
zebraScanner.ensureProfile(); useCallback(() => {
zebraScanner.startListening(); zebraScanner.startListening();
const sub = zebraScanner.addScanListener((scan) => { const sub = zebraScanner.addScanListener((scan) => {
//console.log("SCAN:", scan); //console.log("SCAN:", scan);
handleScan(scan); handleScan(scan);
}); });
return () => { return () => {
sub.remove(); sub.remove();
zebraScanner.stopListening(); zebraScanner.stopListening();
}; };
}, [handleScan]); }, [handleScan]),
);
return ( return (
<View className={`${bgColor ?? ""} flex-1 w-screen`}> <View className={`${bgColor ?? ""} flex-1 w-screen`}>
<View style={{ alignItems: "center", margin: 5 }}> <View style={{ alignItems: "center", margin: 5 }}>

View File

@@ -1,5 +1,6 @@
import axios from "axios"; import axios from "axios";
import { format } from "date-fns-tz"; import { format } from "date-fns-tz";
import { useFocusEffect } from "expo-router";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { Text, View } from "react-native"; import { Text, View } from "react-native";
import { useAppStore } from "../hooks/useAppStore"; import { useAppStore } from "../hooks/useAppStore";
@@ -58,7 +59,9 @@ export default function ProdScanner() {
...scanned.data, ...scanned.data,
runningNumber: scan.data.startsWith("000") runningNumber: scan.data.startsWith("000")
? parseInt(scan.data.slice(10, -1) || "000", 10).toString() ? parseInt(scan.data.slice(10, -1) || "000", 10).toString()
: "0", : scan.data.startsWith("loc")
? scan.data
: "0",
}; };
try { try {
await axios.post( await axios.post(
@@ -118,20 +121,21 @@ export default function ProdScanner() {
//console.log(lastScan); //console.log(lastScan);
useEffect(() => { useFocusEffect(
zebraScanner.ensureProfile(); useCallback(() => {
zebraScanner.startListening(); zebraScanner.startListening();
const sub = zebraScanner.addScanListener((scan) => { const sub = zebraScanner.addScanListener((scan) => {
//console.log("SCAN:", scan); //console.log("SCAN:", scan);
handleScan(scan); handleScan(scan);
}); });
return () => { return () => {
sub.remove(); sub.remove();
zebraScanner.stopListening(); zebraScanner.stopListening();
}; };
}, [handleScan]); }, [handleScan]),
);
return ( return (
<View className={`${bgColor ?? ""} flex-1 w-screen`}> <View className={`${bgColor ?? ""} flex-1 w-screen`}>
<View> <View>

View File

@@ -0,0 +1,140 @@
import { Icon } from '@/components/ui/icon';
import { NativeOnlyAnimatedView } from '@/components/ui/native-only-animated-view';
import { cn } from '@/lib/utils';
import * as DialogPrimitive from '@rn-primitives/dialog';
import { X } from 'lucide-react-native';
import * as React from 'react';
import { Platform, Text, View, type ViewProps } from 'react-native';
import { FadeIn, FadeOut } from 'react-native-reanimated';
import { FullWindowOverlay as RNFullWindowOverlay } from 'react-native-screens';
const Dialog = DialogPrimitive.Root;
const DialogTrigger = DialogPrimitive.Trigger;
const DialogPortal = DialogPrimitive.Portal;
const DialogClose = DialogPrimitive.Close;
const FullWindowOverlay = Platform.OS === 'ios' ? RNFullWindowOverlay : React.Fragment;
function DialogOverlay({
className,
children,
...props
}: Omit<React.ComponentProps<typeof DialogPrimitive.Overlay>, 'asChild'> & {
children?: React.ReactNode;
}) {
return (
<FullWindowOverlay>
<DialogPrimitive.Overlay
className={cn(
'absolute bottom-0 left-0 right-0 top-0 flex items-center justify-center bg-black/50 p-2',
Platform.select({
web: 'animate-in fade-in-0 fixed cursor-default [&>*]:cursor-auto',
}),
className
)}
{...props}
asChild={Platform.OS !== 'web'}>
<NativeOnlyAnimatedView entering={FadeIn.duration(200)} exiting={FadeOut.duration(150)}>
<NativeOnlyAnimatedView entering={FadeIn.delay(50)} exiting={FadeOut.duration(150)}>
<>{children}</>
</NativeOnlyAnimatedView>
</NativeOnlyAnimatedView>
</DialogPrimitive.Overlay>
</FullWindowOverlay>
);
}
function DialogContent({
className,
portalHost,
children,
...props
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
portalHost?: string;
}) {
return (
<DialogPortal hostName={portalHost}>
<DialogOverlay>
<DialogPrimitive.Content
className={cn(
'bg-background border-border z-50 mx-auto flex w-full max-w-[calc(100%-2rem)] flex-col gap-4 rounded-lg border p-6 shadow-lg shadow-black/5 sm:max-w-lg',
Platform.select({
web: 'animate-in fade-in-0 zoom-in-95 duration-200',
}),
className
)}
{...props}>
<>{children}</>
<DialogPrimitive.Close
className={cn(
'absolute right-4 top-4 rounded opacity-70 active:opacity-100',
Platform.select({
web: 'ring-offset-background focus:ring-ring data-[state=open]:bg-accent transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-offset-2',
})
)}
hitSlop={12}>
<Icon
as={X}
className={cn('text-accent-foreground web:pointer-events-none size-4 shrink-0')}
/>
<Text className="sr-only">Close</Text>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogOverlay>
</DialogPortal>
);
}
function DialogHeader({ className, ...props }: ViewProps) {
return (
<View className={cn('flex flex-col gap-2 text-center sm:text-left', className)} {...props} />
);
}
function DialogFooter({ className, ...props }: ViewProps) {
return (
<View
className={cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className)}
{...props}
/>
);
}
function DialogTitle({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
return (
<DialogPrimitive.Title
className={cn('text-foreground text-lg font-semibold leading-none', className)}
{...props}
/>
);
}
function DialogDescription({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
return (
<DialogPrimitive.Description
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
);
}
export {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
};

View File

@@ -0,0 +1,57 @@
import { TextClassContext } from '@/components/ui/text';
import { cn } from '@/lib/utils';
import type { LucideIcon, LucideProps } from 'lucide-react-native';
import { cssInterop } from 'nativewind';
import * as React from 'react';
type IconProps = LucideProps & {
as: LucideIcon;
} & React.RefAttributes<LucideIcon>;
function IconImpl({ as: IconComponent, ...props }: IconProps) {
return <IconComponent {...props} />;
}
cssInterop(IconImpl, {
className: {
target: 'style',
nativeStyleToProp: {
height: 'size',
width: 'size',
},
},
});
/**
* A wrapper component for Lucide icons with Nativewind `className` support via `cssInterop`.
*
* This component allows you to render any Lucide icon while applying utility classes
* using `nativewind`. It avoids the need to wrap or configure each icon individually.
*
* @component
* @example
* ```tsx
* import { ArrowRight } from 'lucide-react-native';
* import { Icon } from '@/registry/components/ui/icon';
*
* <Icon as={ArrowRight} className="text-red-500" size={16} />
* ```
*
* @param {LucideIcon} as - The Lucide icon component to render.
* @param {string} className - Utility classes to style the icon using Nativewind.
* @param {number} size - Icon size (defaults to 14).
* @param {...LucideProps} ...props - Additional Lucide icon props passed to the "as" icon.
*/
function Icon({ as: IconComponent, className, size = 14, ...props }: IconProps) {
const textClass = React.useContext(TextClassContext);
return (
<IconImpl
as={IconComponent}
className={cn('text-foreground', textClass, className)}
size={size}
{...props}
/>
);
}
export { Icon };

View File

@@ -0,0 +1,23 @@
import { Platform } from 'react-native';
import Animated from 'react-native-reanimated';
/**
* This component is used to wrap animated views that should only be animated on native.
* @param props - The props for the animated view.
* @returns The animated view if the platform is native, otherwise the children.
* @example
* <NativeOnlyAnimatedView entering={FadeIn} exiting={FadeOut}>
* <Text>I am only animated on native</Text>
* </NativeOnlyAnimatedView>
*/
function NativeOnlyAnimatedView(
props: React.ComponentProps<typeof Animated.View> & React.RefAttributes<typeof Animated.View>
) {
if (Platform.OS === 'web') {
return <>{props.children as React.ReactNode}</>;
} else {
return <Animated.View {...props} />;
}
}
export { NativeOnlyAnimatedView };

View File

@@ -1,5 +1,5 @@
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import * as Slot from '@rn-primitives/slot'; import { Slot } from '@rn-primitives/slot';
import { cva, type VariantProps } from 'class-variance-authority'; import { cva, type VariantProps } from 'class-variance-authority';
import * as React from 'react'; import * as React from 'react';
import { Platform, Text as RNText, type Role } from 'react-native'; import { Platform, Text as RNText, type Role } from 'react-native';
@@ -70,11 +70,12 @@ function Text({
variant = 'default', variant = 'default',
...props ...props
}: React.ComponentProps<typeof RNText> & }: React.ComponentProps<typeof RNText> &
React.RefAttributes<typeof RNText> &
TextVariantProps & { TextVariantProps & {
asChild?: boolean; asChild?: boolean;
}) { }) {
const textClass = React.useContext(TextClassContext); const textClass = React.useContext(TextClassContext);
const Component = asChild ? Slot.Text : RNText; const Component = asChild ? Slot : RNText;
return ( return (
<Component <Component
className={cn(textVariants({ variant }), textClass, className)} className={cn(textVariants({ variant }), textClass, className)}