This commit is contained in:
payacom
2026-07-16 20:08:49 +03:30
parent 39b12a0986
commit 58314d0ab5
40 changed files with 986 additions and 535 deletions

View File

@@ -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");
}

View 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,
};

View File

@@ -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);

View 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";
}
}

View File

@@ -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 {

View File

@@ -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[] = [];

View File

@@ -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}`,
});
}

View File

@@ -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 };

View 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(" ");
}