Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import React from "react";
import UserPageRepNewRepSearchDropdown from "@/components/user/rep/new-rep/UserPageRepNewRepSearchDropdown";
import { RepSearchState } from "@/components/user/rep/new-rep/UserPageRepNewRepSearch";
import { RepSearchState } from "@/components/user/rep/new-rep/rep-search-types";

describe("UserPageRepNewRepSearchDropdown", () => {
it("renders categories and handles selection", async () => {
Expand Down
58 changes: 44 additions & 14 deletions components/common/OverlappingAvatars.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
import { getScaledImageUri, ImageScale } from "@/helpers/image.helpers";
import { TOOLTIP_STYLES } from "@/helpers/tooltip.helpers";
import useIsTouchDevice from "@/hooks/useIsTouchDevice";
import Image from "next/image";
import Link from "next/link";
import type { MouseEvent, ReactNode } from "react";
import { useId } from "react";
import { useId, useState } from "react";
import { Tooltip } from "react-tooltip";

interface OverlappingAvatarItem {
Expand Down Expand Up @@ -34,6 +35,42 @@ const SIZE_CLASS = {
md: "tw-h-7 tw-w-7",
} as const;

function AvatarContent({
pfpUrl,
ariaLabel,
fallback,
avatarRing,
}: {
readonly pfpUrl: string | null;
readonly ariaLabel?: string | undefined;
readonly fallback?: string | undefined;
readonly avatarRing: string;
}) {
const [imgError, setImgError] = useState(false);

if (!pfpUrl || imgError) {
return (
<div className="tw-flex tw-h-full tw-w-full tw-items-center tw-justify-center tw-rounded-full tw-bg-iron-700 tw-ring-[1.5px] tw-ring-black">
<span className="tw-text-[0.625rem] tw-font-semibold tw-text-iron-300">
{fallback ?? "?"}
</span>
</div>
);
}

return (
<Image
src={getScaledImageUri(pfpUrl, ImageScale.W_AUTO_H_50)}
alt={ariaLabel ?? "Profile"}
fill
sizes="28px"
unoptimized
onError={() => setImgError(true)}
className={`tw-object-cover tw-rounded-full ${avatarRing}`}
/>
);
}

export default function OverlappingAvatars({
items,
maxCount = 5,
Expand All @@ -60,20 +97,13 @@ export default function OverlappingAvatars({
else if (index === slice.length - 1) transformOrigin = "right center";
else transformOrigin = "center center";

const content = item.pfpUrl ? (
<img
src={getScaledImageUri(item.pfpUrl, ImageScale.W_AUTO_H_50)}
alt={item.ariaLabel ?? "Profile"}
loading="lazy"
decoding="async"
className={`tw-h-full tw-w-full tw-flex-shrink-0 tw-rounded-full ${avatarRing}`}
const content = (
<AvatarContent
pfpUrl={item.pfpUrl}
ariaLabel={item.ariaLabel}
fallback={item.fallback}
avatarRing={avatarRing}
/>
) : (
<div className="tw-flex tw-h-full tw-w-full tw-items-center tw-justify-center tw-rounded-full tw-bg-iron-700 tw-ring-[1.5px] tw-ring-black">
<span className="tw-text-[0.625rem] tw-font-semibold tw-text-iron-300">
{item.fallback ?? "?"}
</span>
</div>
);

const showTooltip =
Expand Down
4 changes: 2 additions & 2 deletions components/user/rep/header/UserPageRepHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,10 @@ export default function UserPageRepHeader({
<button
type="button"
onClick={() => setIsGrantRepOpen(true)}
className="tw-group tw-inline-flex tw-h-11 tw-cursor-pointer tw-items-center tw-justify-center tw-gap-x-1.5 tw-rounded-lg tw-border tw-border-dashed tw-border-white/10 tw-bg-transparent tw-px-4 tw-text-sm tw-font-medium tw-text-iron-400 tw-transition-all tw-duration-300 tw-ease-out hover:tw-border-white/30 hover:tw-bg-white/5 hover:tw-text-white"
className="tw-group tw-inline-flex tw-h-11 tw-cursor-pointer tw-items-center tw-justify-center tw-gap-x-1.5 tw-rounded-lg tw-border tw-border-dashed tw-border-white/15 tw-bg-white/[0.03] tw-px-4 tw-text-sm tw-font-medium tw-text-iron-400 tw-transition-all tw-duration-300 tw-ease-out hover:tw-border-white/20 hover:tw-bg-white/[0.05] hover:tw-text-iron-300"
>
<svg
className="-tw-ml-1 tw-h-4 tw-w-4 tw-text-iron-500 tw-transition-colors group-hover:tw-text-white"
className="-tw-ml-1 tw-h-4 tw-w-4 tw-text-iron-400 tw-transition-colors group-hover:tw-text-white"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
Expand Down
26 changes: 8 additions & 18 deletions components/user/rep/new-rep/GrantRepDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,14 @@ export default function GrantRepDialog({
onClose={onClose}
tabletModal
>
<div className="tw-px-4 sm:tw-px-6">
<UserPageRateWrapper profile={profile} type={RateMatter.REP}>
<UserPageRepNewRep
profile={profile}
repRates={repRates}
onSuccess={onClose}
/>
</UserPageRateWrapper>
<div className="tw-mt-3">
<button
onClick={onClose}
type="button"
className="tw-w-full tw-cursor-pointer tw-rounded-lg tw-border tw-border-solid tw-border-iron-700 tw-bg-iron-900 tw-px-4 tw-py-3 tw-text-sm tw-font-semibold tw-text-white tw-transition tw-duration-300 tw-ease-out hover:tw-bg-iron-800"
>
Cancel
</button>
</div>
</div>
<UserPageRateWrapper profile={profile} type={RateMatter.REP}>
<UserPageRepNewRep
profile={profile}
repRates={repRates}
onSuccess={onClose}
onCancel={onClose}
/>
</UserPageRateWrapper>
</MobileWrapperDialog>
);
}
7 changes: 4 additions & 3 deletions components/user/rep/new-rep/UserPageRepNewRep.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
"use client";

import type {
ApiProfileRepRatesState,
} from "@/entities/IProfile";
import type { ApiProfileRepRatesState } from "@/entities/IProfile";
import UserPageRepNewRepSearch from "./UserPageRepNewRepSearch";
import type { ApiIdentity } from "@/generated/models/ApiIdentity";

export default function UserPageRepNewRep({
profile,
repRates,
onSuccess,
onCancel,
}: {
readonly profile: ApiIdentity;
readonly repRates: ApiProfileRepRatesState | null;
readonly onSuccess?: () => void;
readonly onCancel?: () => void;
}) {
const searchProps = onSuccess ? { onSuccess } : {};

return (
<UserPageRepNewRepSearch
repRates={repRates}
profile={profile}
onCancel={onCancel}
{...searchProps}
/>
);
Expand Down
Loading