login
This commit is contained in:
@@ -5,6 +5,21 @@ type ProxyOptions = {
|
||||
stripAuth?: boolean;
|
||||
};
|
||||
|
||||
const PUBLIC_AUTH_PATTERNS = [
|
||||
/^\/login\/?$/,
|
||||
/^\/login\/verify$/,
|
||||
/^\/login\/login_username$/,
|
||||
/^\/login\/forgot_password$/,
|
||||
/^\/register\/?$/,
|
||||
/^\/register\/verify$/,
|
||||
/^\/auth\/google$/,
|
||||
];
|
||||
|
||||
export function isPublicAuthPath(pathSegments: string[]): boolean {
|
||||
const path = `/${pathSegments.join("/")}`;
|
||||
return PUBLIC_AUTH_PATTERNS.some((pattern) => pattern.test(path));
|
||||
}
|
||||
|
||||
export async function proxyToUpstream(
|
||||
req: Request,
|
||||
upstreamPath: string,
|
||||
@@ -43,8 +58,3 @@ export async function proxyToUpstream(
|
||||
headers: responseHeaders,
|
||||
});
|
||||
}
|
||||
|
||||
export function isPublicAuthPath(pathSegments: string[]): boolean {
|
||||
const path = pathSegments.join("/");
|
||||
return path.startsWith("login") || path.startsWith("register");
|
||||
}
|
||||
|
||||
6
src/lib/auth/inputProps.ts
Normal file
6
src/lib/auth/inputProps.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export const MOBILE_NUMERIC_INPUT_PROPS = {
|
||||
type: "tel" as const,
|
||||
inputMode: "numeric" as const,
|
||||
pattern: "[0-9]*",
|
||||
autoComplete: "tel-national" as const,
|
||||
};
|
||||
@@ -2,6 +2,7 @@ import type { AppRouterInstance } from "next/dist/shared/lib/app-router-context.
|
||||
import type { IVerifyOtp } from "@/types/types";
|
||||
import { setAuthSession } from "@/lib/auth/session";
|
||||
import { AUTH_SUCCESS_DELAY_MS, pause } from "@/lib/auth/feedback";
|
||||
import { getRegistrationRoute } from "@/lib/auth/registrationRoutes";
|
||||
|
||||
export function getSafeRedirectPath(): string {
|
||||
if (typeof window === "undefined") return "/";
|
||||
@@ -16,23 +17,7 @@ export async function navigateIncompleteRegistration(
|
||||
router: AppRouterInstance,
|
||||
step?: string
|
||||
) {
|
||||
switch (step) {
|
||||
case "user_name":
|
||||
router.push("/register/username");
|
||||
break;
|
||||
case "password":
|
||||
router.push("/register/password");
|
||||
break;
|
||||
case "first_name":
|
||||
router.push("/register/fullname");
|
||||
break;
|
||||
case "user_type":
|
||||
router.push("/register/usertype");
|
||||
break;
|
||||
default:
|
||||
router.push("/verify/avatar");
|
||||
break;
|
||||
}
|
||||
router.push(getRegistrationRoute(step));
|
||||
}
|
||||
|
||||
export async function completeLogin(
|
||||
@@ -46,6 +31,8 @@ export async function completeLogin(
|
||||
id: response.id,
|
||||
user_type: response.user_type,
|
||||
step: response.step,
|
||||
auth_provider: response.auth_provider,
|
||||
email: response.email ?? undefined,
|
||||
});
|
||||
|
||||
await pause(AUTH_SUCCESS_DELAY_MS);
|
||||
|
||||
16
src/lib/auth/registrationRoutes.ts
Normal file
16
src/lib/auth/registrationRoutes.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export function getRegistrationRoute(step?: string): string {
|
||||
switch (step) {
|
||||
case "user_name":
|
||||
return "/register/username";
|
||||
case "password":
|
||||
return "/register/password";
|
||||
case "first_name":
|
||||
return "/register/fullname";
|
||||
case "registration_choice":
|
||||
return "/register/complete";
|
||||
case "user_type":
|
||||
return "/register/complete";
|
||||
default:
|
||||
return "/register/username";
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@ export type AuthUserMeta = {
|
||||
id?: string;
|
||||
user_type?: string;
|
||||
step?: string;
|
||||
auth_provider?: string;
|
||||
email?: string;
|
||||
};
|
||||
|
||||
const cookieOptions = (): Cookies.CookieAttributes => ({
|
||||
@@ -34,6 +36,8 @@ export async function setAuthSession(
|
||||
if (meta?.id) localStorage.setItem("id", meta.id);
|
||||
if (meta?.user_type) localStorage.setItem("usertype", meta.user_type);
|
||||
if (meta?.step) localStorage.setItem("step", meta.step);
|
||||
if (meta?.auth_provider) localStorage.setItem("auth_provider", meta.auth_provider);
|
||||
if (meta?.email) localStorage.setItem("email", meta.email);
|
||||
}
|
||||
|
||||
/** پاک کردن کامل session */
|
||||
@@ -58,6 +62,8 @@ export async function clearAuthSession(): Promise<void> {
|
||||
localStorage.removeItem("last_name");
|
||||
localStorage.removeItem("username");
|
||||
localStorage.removeItem("step");
|
||||
localStorage.removeItem("auth_provider");
|
||||
localStorage.removeItem("email");
|
||||
}
|
||||
|
||||
export function getStoredUserId(): string {
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
import type { ChatMessage } from "@/components/chat/ChatMessageCard";
|
||||
|
||||
function matchesPendingServer(server: ChatMessage, pending: ChatMessage): boolean {
|
||||
if (String(server.senderId) !== String(pending.senderId)) return false;
|
||||
if (pending.fileType !== server.fileType) return false;
|
||||
if (pending.fileType === "location" || (!pending.fileType && !pending.file)) {
|
||||
return server.content === pending.content;
|
||||
}
|
||||
return Boolean(pending.fileType && server.fileType === pending.fileType);
|
||||
}
|
||||
|
||||
export function filterResolvedPendingMessages(
|
||||
server: ChatMessage[],
|
||||
pending: ChatMessage[]
|
||||
): ChatMessage[] {
|
||||
return pending.filter(
|
||||
(p) =>
|
||||
!server.some((s) => s._id === p._id || matchesPendingServer(s, p))
|
||||
);
|
||||
}
|
||||
|
||||
export function dedupeChatMessages(messages: ChatMessage[]): ChatMessage[] {
|
||||
const seen = new Set<string>();
|
||||
const result: ChatMessage[] = [];
|
||||
|
||||
@@ -8,8 +8,8 @@ export type MessageGroup =
|
||||
| { type: "date"; label: string; key: string }
|
||||
| { type: "message"; message: ChatMessage; key: string };
|
||||
|
||||
function dayKey(createdAt?: string | null): string {
|
||||
const d = parseMessageDate(createdAt);
|
||||
function dayKey(msg: ChatMessage): string {
|
||||
const d = parseMessageDate(msg.createdAtIso ?? msg.createdAt);
|
||||
if (d) return d.toDateString();
|
||||
return "unknown-day";
|
||||
}
|
||||
@@ -21,12 +21,12 @@ export function groupMessagesByDate(messages: ChatMessage[]): MessageGroup[] {
|
||||
messages.forEach((msg, i) => {
|
||||
if (!msg) return;
|
||||
|
||||
const dk = dayKey(msg.createdAt);
|
||||
const dk = dayKey(msg);
|
||||
if (dk !== lastDay) {
|
||||
lastDay = dk;
|
||||
groups.push({
|
||||
type: "date",
|
||||
label: formatChatDateLabel(msg.createdAt),
|
||||
label: formatChatDateLabel(msg.createdAtIso ?? msg.createdAt),
|
||||
key: `date-${dk}`,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ import { parseMessageDate } from "@/lib/chat/formatMessageTime";
|
||||
|
||||
export type BubbleGroupPosition = "single" | "first" | "middle" | "last";
|
||||
|
||||
function dayKey(createdAt?: string | null): string {
|
||||
const d = parseMessageDate(createdAt);
|
||||
function dayKey(msg: ChatMessage): string {
|
||||
const d = parseMessageDate(msg.createdAtIso ?? msg.createdAt);
|
||||
if (d) return d.toDateString();
|
||||
return "unknown-day";
|
||||
}
|
||||
@@ -14,7 +14,7 @@ export function sameSender(a: ChatMessage, b: ChatMessage): boolean {
|
||||
}
|
||||
|
||||
function sameVisualGroup(a: ChatMessage, b: ChatMessage): boolean {
|
||||
return sameSender(a, b) && dayKey(a.createdAt) === dayKey(b.createdAt);
|
||||
return sameSender(a, b) && dayKey(a) === dayKey(b);
|
||||
}
|
||||
|
||||
export { sameVisualGroup };
|
||||
|
||||
9
src/lib/formatFullName.ts
Normal file
9
src/lib/formatFullName.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export function formatFullName(
|
||||
firstName?: string | null,
|
||||
lastName?: string | null
|
||||
): string {
|
||||
return [firstName, lastName]
|
||||
.map((part) => (part == null ? "" : String(part).trim()))
|
||||
.filter((part) => part.length > 0 && part.toLowerCase() !== "null")
|
||||
.join(" ");
|
||||
}
|
||||
Reference in New Issue
Block a user