login time
This commit is contained in:
@@ -4,7 +4,7 @@ interface AuthHeaderProps {
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export const AuthHeader = ({ title = "Project Athena" }: AuthHeaderProps) => {
|
||||
export const AuthHeader = ({ title = "Bird Eye View" }: AuthHeaderProps) => {
|
||||
return (
|
||||
<View style={{ alignItems: "center", marginBottom: 36 }}>
|
||||
<Image
|
||||
|
||||
@@ -17,7 +17,7 @@ export const ForgotPasswordLink = ({
|
||||
onPress={onPress}
|
||||
disabled={disabled || isResetting}
|
||||
>
|
||||
<Text className="text-primaryPurple">
|
||||
<Text className="text-primaryPurpleDark font-bold">
|
||||
{isResetting ? "Sending reset email..." : "Forgot Password?"}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { useRef, useState } from "react";
|
||||
import {
|
||||
Platform,
|
||||
NativeSyntheticEvent,
|
||||
@@ -32,6 +32,20 @@ export const PasswordInput = ({
|
||||
submitBehavior,
|
||||
}: PasswordInputProps) => {
|
||||
const [secureTextEntry, setSecureTextEntry] = useState(true);
|
||||
const submitGuardRef = useRef(false);
|
||||
|
||||
const triggerSubmitOnce = () => {
|
||||
if (submitGuardRef.current) return;
|
||||
submitGuardRef.current = true;
|
||||
try {
|
||||
onSubmitEditing?.();
|
||||
} finally {
|
||||
// release guard on next tick to allow future submissions
|
||||
setTimeout(() => {
|
||||
submitGuardRef.current = false;
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TextInput
|
||||
@@ -46,12 +60,12 @@ export const PasswordInput = ({
|
||||
backgroundColor: "transparent",
|
||||
...style,
|
||||
}}
|
||||
onSubmitEditing={onSubmitEditing}
|
||||
onSubmitEditing={() => triggerSubmitOnce()}
|
||||
returnKeyType={returnKeyType}
|
||||
submitBehavior={submitBehavior ?? "submit"}
|
||||
onKeyPress={(e) => {
|
||||
if (Platform.OS === "web" && e?.nativeEvent?.key === "Enter") {
|
||||
onSubmitEditing?.();
|
||||
triggerSubmitOnce();
|
||||
}
|
||||
}}
|
||||
left={<TextInput.Icon icon="lock" color="#6750a4" />}
|
||||
|
||||
@@ -71,7 +71,7 @@ export const SignInForm = ({
|
||||
returnKeyType="next"
|
||||
/>
|
||||
{error ? (
|
||||
<Text style={{ color: "#F83434", marginBottom: 8 }}>
|
||||
<Text style={{ color: "#F83434", marginTop: -4, marginBottom: 8 }}>
|
||||
{error.message}
|
||||
</Text>
|
||||
) : null}
|
||||
@@ -92,11 +92,10 @@ export const SignInForm = ({
|
||||
disabled={isLoading}
|
||||
style={{ marginBottom: 8 }}
|
||||
returnKeyType="done"
|
||||
submitBehavior="blurAndSubmit"
|
||||
onSubmitEditing={() => handleSubmit(submit)()}
|
||||
/>
|
||||
{error ? (
|
||||
<Text style={{ color: "#F83434", marginBottom: 8 }}>
|
||||
<Text style={{ color: "#F83434", marginTop: -4, marginBottom: 8 }}>
|
||||
{error.message}
|
||||
</Text>
|
||||
) : null}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { View, Text } from "react-native";
|
||||
|
||||
import { UserLoginLog } from "@/store/api/usersApi";
|
||||
import { formatDate, formatTimeWithSeconds } from "@/utils/dateUtils";
|
||||
import { reasonLabel } from "@/utils/loginUtils";
|
||||
|
||||
interface LoginListItemProps {
|
||||
log: UserLoginLog;
|
||||
}
|
||||
|
||||
export const LoginListItem = ({ log }: LoginListItemProps) => {
|
||||
const isSuccess = log.status === "SUCCESS";
|
||||
const isFailure = log.status === "FAILURE";
|
||||
|
||||
return (
|
||||
<View className="bg-white rounded-lg p-1 mb-3 shadow-sm">
|
||||
<View className="flex-row items-center">
|
||||
<Text
|
||||
className="text-sm font-medium text-gray-700 flex-1"
|
||||
numberOfLines={1}
|
||||
ellipsizeMode="tail"
|
||||
>
|
||||
{formatDate(log.lastLoginAt)} -{" "}
|
||||
{formatTimeWithSeconds(log.lastLoginAt)} {" | "}
|
||||
<Text
|
||||
style={{
|
||||
color: isSuccess ? "#22C927" : "#F83434",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
{isSuccess
|
||||
? log.activity
|
||||
: reasonLabel(log.failureReason) || "Failed"}
|
||||
</Text>
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -1,16 +1,37 @@
|
||||
import { View, Text } from "react-native";
|
||||
|
||||
import { User } from "@/store/models/User.model";
|
||||
import { useGetUserLogsByIdQuery } from "@/store/api/usersApi";
|
||||
import { LoginListItem } from "@/components/features/accounts/LoginListItem";
|
||||
|
||||
interface PersonalLoginListProps {
|
||||
user: User;
|
||||
}
|
||||
|
||||
export const PersonalLoginList = ({ user }: PersonalLoginListProps) => {
|
||||
const { data, isLoading, isError } = useGetUserLogsByIdQuery(
|
||||
{ userId: user.id, page: 1, limit: 10 },
|
||||
{ skip: !user?.id }
|
||||
);
|
||||
|
||||
const items = data?.items ?? [];
|
||||
|
||||
return (
|
||||
<View className="bg-lightGray rounded-md">
|
||||
<Text className="font-medium my-4 text-gray-600">Log-in List:</Text>
|
||||
<Text className="font-medium text-black">No logins assigned</Text>
|
||||
<Text className="font-medium mb-4 mt-0 text-gray-600">Log-in List:</Text>
|
||||
{isLoading ? (
|
||||
<Text className="text-black">Loading...</Text>
|
||||
) : isError ? (
|
||||
<Text className="text-black">-</Text>
|
||||
) : items.length === 0 ? (
|
||||
<Text className="font-medium text-black">No logins history</Text>
|
||||
) : (
|
||||
<View style={{ gap: 8 }}>
|
||||
{items.map((log, idx) => (
|
||||
<LoginListItem key={`${log.id}-${idx}`} log={log} />
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import { InfoRow } from "./InfoRow";
|
||||
import { useAppSelector } from "@/store/hooks";
|
||||
import { selectIsMobile } from "@/store/screen/screenSizeSlice";
|
||||
import { useGetUserLogsQuery } from "@/store/api/usersApi";
|
||||
import { formatDate, formatTime } from "@/utils/dateUtils";
|
||||
import { formatDate, formatTimeWithSeconds } from "@/utils/dateUtils";
|
||||
import { RiskIndicator } from "@/components/ui/RiskIndicator";
|
||||
import { SizeEnum } from "@/constants/sizeEnum";
|
||||
import { RiskEnum, RiskLabels } from "@/constants/riskEnum";
|
||||
@@ -35,7 +35,7 @@ export const PersonalMetrics = ({ user }: PersonalMetricsProps) => {
|
||||
: logsError
|
||||
? "-"
|
||||
: lastLoginAt
|
||||
? `${formatDate(lastLoginAt)} ${formatTime(lastLoginAt)}`
|
||||
? `${formatDate(lastLoginAt)} ${formatTimeWithSeconds(lastLoginAt)}`
|
||||
: "-";
|
||||
|
||||
const lastPasswordChangeDisplay = logsLoading
|
||||
@@ -43,7 +43,7 @@ export const PersonalMetrics = ({ user }: PersonalMetricsProps) => {
|
||||
: logsError
|
||||
? "-"
|
||||
: activity === "Password reset" && lastLoginAt
|
||||
? `${formatDate(lastLoginAt)} ${formatTime(lastLoginAt)}`
|
||||
? `${formatDate(lastLoginAt)} ${formatTimeWithSeconds(lastLoginAt)}`
|
||||
: "-";
|
||||
|
||||
return (
|
||||
|
||||
@@ -23,7 +23,7 @@ export const PersonalTodoList = ({ user }: PersonalTodoListProps) => {
|
||||
|
||||
return (
|
||||
<View className="bg-lightGray rounded-md">
|
||||
<Text className="font-medium my-4 text-gray-600">To-Do List:</Text>
|
||||
<Text className="font-medium mt-0 mb-4 text-gray-600">To-Do List:</Text>
|
||||
{isLoading ? (
|
||||
<LoadingSpinner />
|
||||
) : error ? (
|
||||
|
||||
@@ -2,9 +2,10 @@ import { DataTable, Text } from "react-native-paper";
|
||||
import { View } from "react-native";
|
||||
|
||||
import { UserLoginLog } from "@/store/api/usersApi";
|
||||
import { formatDate, formatTime } from "@/utils/dateUtils";
|
||||
import { formatDate, formatTimeWithSeconds } from "@/utils/dateUtils";
|
||||
import { LogsTableHeader } from "@/components/features/logs/LogsTableHeader";
|
||||
import { TableSortDirection } from "@/constants/tableSortDirectionEnum";
|
||||
import { reasonLabel } from "@/utils/loginUtils";
|
||||
|
||||
interface DesktopLogsTableProps {
|
||||
logs: UserLoginLog[];
|
||||
@@ -42,13 +43,21 @@ export const DesktopLogsTable = ({
|
||||
<DataTable.Cell>
|
||||
<View className="flex-row items-center flex-shrink pr-2">
|
||||
<Text numberOfLines={1}>
|
||||
{formatDate(log.lastLoginAt)} {formatTime(log.lastLoginAt)}
|
||||
{formatDate(log.lastLoginAt)} {formatTimeWithSeconds(log.lastLoginAt)}
|
||||
</Text>
|
||||
</View>
|
||||
</DataTable.Cell>
|
||||
<DataTable.Cell>
|
||||
<View className="flex-row items-center flex-shrink pr-2">
|
||||
<Text numberOfLines={1}>{log.activity}</Text>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={{ color: log.status === "FAILURE" ? "#F83434" : "#22C927" }}
|
||||
>
|
||||
{log.activity}
|
||||
{log.status === "FAILURE" && reasonLabel(log.failureReason)
|
||||
? ` - ${reasonLabel(log.failureReason)}`
|
||||
: ""}
|
||||
</Text>
|
||||
</View>
|
||||
</DataTable.Cell>
|
||||
</DataTable.Row>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { ScrollView, Text, View } from "react-native";
|
||||
|
||||
import { UserLoginLog } from "@/store/api/usersApi";
|
||||
import { formatDate, formatTime } from "@/utils/dateUtils";
|
||||
import { formatDate, formatTimeWithSeconds } from "@/utils/dateUtils";
|
||||
import { reasonLabel } from "@/utils/loginUtils";
|
||||
|
||||
interface MobileLogsTableProps {
|
||||
logs: UserLoginLog[];
|
||||
@@ -21,10 +22,16 @@ export const MobileLogsTable = ({ logs }: MobileLogsTableProps) => {
|
||||
<Text className="font-normal text-gray-600">({log.email})</Text>
|
||||
</Text>
|
||||
<Text className="text-sm text-gray-700 mt-1">
|
||||
Time: {formatDate(log.lastLoginAt)} {formatTime(log.lastLoginAt)}
|
||||
Time: {formatDate(log.lastLoginAt)} {formatTimeWithSeconds(log.lastLoginAt)}
|
||||
</Text>
|
||||
<Text className="text-sm text-gray-700 mt-1">
|
||||
<Text
|
||||
className="text-sm mt-1"
|
||||
style={{ color: log.status === "FAILURE" ? "#F83434" : "#22C927" }}
|
||||
>
|
||||
Login activity: {log.activity}
|
||||
{log.status === "FAILURE" && reasonLabel(log.failureReason)
|
||||
? ` - ${reasonLabel(log.failureReason)}`
|
||||
: ""}
|
||||
</Text>
|
||||
</View>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user