Skip to content

Commit

Permalink
Merge branch 'main' into fix/category-edit-issue
Browse files Browse the repository at this point in the history
  • Loading branch information
DonKoko authored Dec 9, 2024
2 parents 62f0cf5 + 055da46 commit 53f357a
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 12 deletions.
5 changes: 4 additions & 1 deletion app/modules/asset/service.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2573,8 +2573,9 @@ export async function bulkCheckOutAssets({
throw new ShelfError({
cause: null,
message:
"There are sone unavailable assets. Please make sure you are selecting only available assets.",
"There are some unavailable assets. Please make sure you are selecting only available assets.",
label: "Assets",
shouldBeCaptured: false,
});
}

Expand Down Expand Up @@ -2670,6 +2671,7 @@ export async function bulkCheckInAssets({
message:
"There are some assets without custody. Please make sure you are selecting assets with custody.",
label: "Assets",
shouldBeCaptured: false,
});
}

Expand Down Expand Up @@ -2983,6 +2985,7 @@ export async function relinkQrCode({
message:
"You cannot link to this code because its already linked to another asset. Delete the other asset to free up the code and try again.",
label: "QR",
shouldBeCaptured: false,
});
}

Expand Down
39 changes: 30 additions & 9 deletions app/modules/team-member/service.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,18 +392,39 @@ async function fixTeamMembersNames(teamMembers: TeamMemberWithUserData[]) {
if (teamMembersWithEmptyNames.length === 0) return;

/**
* Itterate over the members and update them without awaiting
* Just in case we check again
* Updates team member names by:
* 1. Using first + last name if both exist
* 2. Using just first or last name if one exists
* 3. Falling back to email username if no name exists
* 4. Using "Unknown" as last resort if no email exists
*/
await Promise.all(
teamMembersWithEmptyNames.map((teamMember) => {
const name = teamMember.user
? `${teamMember.user.firstName} ${teamMember.user.lastName}`
: "Unknown name";
return db.teamMember.update({
where: { id: teamMember.id },
data: { name },
});
let name: string;

if (teamMember.user) {
const { firstName, lastName, email } = teamMember.user;

if (firstName?.trim() || lastName?.trim()) {
// At least one name exists - concatenate available names
name = [firstName?.trim(), lastName?.trim()]
.filter(Boolean)
.join(" ");
} else {
// No names but email exists - use email username
name = email.split("@")[0];
// Optionally improve email username readability
name = name
.replace(/[._]/g, " ") // Replace dots/underscores with spaces
.replace(/\b\w/g, (c) => c.toUpperCase()); // Capitalize words
}

return db.teamMember.update({
where: { id: teamMember.id },
data: { name },
});
}
return null;
})
);

Expand Down
40 changes: 40 additions & 0 deletions app/modules/user/utils.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { sendEmail } from "~/emails/mail.server";
import { sendNotification } from "~/utils/emitter/send-notification.server";
import { ShelfError } from "~/utils/error";
import { data, parseData } from "~/utils/http.server";
import { randomUsernameFromEmail } from "~/utils/user";
import { revokeAccessToOrganization } from "./service.server";
import { revokeAccessEmailText } from "../invite/helpers";
import { createInvite } from "../invite/service.server";
Expand Down Expand Up @@ -215,3 +216,42 @@ export async function resolveUserAction(
}
}
}

/**
* Maximum number of attempts to generate a unique username
* This prevents infinite loops while still providing multiple retry attempts
*/
const MAX_USERNAME_ATTEMPTS = 5;

/**
* Generates a unique username for a new user with retry mechanism
* @param email - User's email to base username on
* @returns Unique username or throws if cannot generate after max attempts
* @throws {ShelfError} If unable to generate unique username after max attempts
*/
export async function generateUniqueUsername(email: string): Promise<string> {
let attempts = 0;

while (attempts < MAX_USERNAME_ATTEMPTS) {
const username = randomUsernameFromEmail(email);

// Check if username exists
const existingUser = await db.user.findUnique({
where: { username },
select: { id: true },
});

if (!existingUser) {
return username;
}

attempts++;
}

throw new ShelfError({
cause: null,
message: "Unable to generate unique username after maximum attempts",
label: "User",
additionalData: { email, attempts: MAX_USERNAME_ATTEMPTS },
});
}
5 changes: 3 additions & 2 deletions app/routes/_auth+/otp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { verifyOtpAndSignin } from "~/modules/auth/service.server";
import { setSelectedOrganizationIdCookie } from "~/modules/organization/context.server";
import { getOrganizationByUserId } from "~/modules/organization/service.server";
import { createUser, findUserByEmail } from "~/modules/user/service.server";
import { generateUniqueUsername } from "~/modules/user/utils.server";
import { appendToMetaTitle } from "~/utils/append-to-meta-title";
import { setCookie } from "~/utils/cookies.server";
import { makeShelfError, notAllowedMethod } from "~/utils/error";
Expand All @@ -30,7 +31,6 @@ import {
import { validEmail } from "~/utils/misc";
import { getOtpPageData, type OtpVerifyMode } from "~/utils/otp";
import { tw } from "~/utils/tw";
import { randomUsernameFromEmail } from "~/utils/user";
import type { action as resendOtpAction } from "./resend-otp";

export function loader({ context, request }: LoaderFunctionArgs) {
Expand Down Expand Up @@ -70,9 +70,10 @@ export async function action({ context, request }: ActionFunctionArgs) {
const userExists = Boolean(await findUserByEmail(email));

if (!userExists) {
const username = await generateUniqueUsername(authSession.email);
await createUser({
...authSession,
username: randomUsernameFromEmail(authSession.email),
username,
});
}

Expand Down
1 change: 1 addition & 0 deletions app/routes/_layout+/account-details.general.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ export async function action({ context, request }: ActionFunctionArgs) {
: "Failed to initiate email change",
additionalData: { userId, newEmail },
label: "Auth",
shouldBeCaptured: !emailExists,
});
}

Expand Down
1 change: 1 addition & 0 deletions app/routes/_layout+/settings.team.users.invite-user.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ export const action = async ({ context, request }: ActionFunctionArgs) => {
"User already has a pending invite. Either resend it or cancel it in order to be able to send a new one.",
additionalData: { email, organizationId },
label: "Invite",
shouldBeCaptured: false,
});
}

Expand Down

0 comments on commit 53f357a

Please sign in to comment.