Skip to content
Draft
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
147 changes: 100 additions & 47 deletions src/modules/approval-requests/ApprovalRequestPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,30 @@ import ApproverActions from "./components/approval-request/ApproverActions";
import { useApprovalRequest } from "./hooks/useApprovalRequest";
import type { Organization } from "../ticket-fields/data-types/Organization";
import ApprovalRequestBreadcrumbs from "./components/approval-request/ApprovalRequestBreadcrumbs";
import ClarificationContainer from "./components/approval-request/clarification/ClarificationContainer";
import type { ApprovalClarificationFlowMessage } from "./types";
import { getColor } from "@zendeskgarden/react-theming";

const Container = styled.div`
display: flex;
flex-direction: row;
display: grid;
grid-template-columns: 2fr 1fr;
grid-template-areas:
"left right"
"approverActions right"
"clarification right";

grid-gap: ${(props) => props.theme.space.lg};
margin-top: ${(props) => props.theme.space.xl}; /* 40px */
margin-bottom: ${(props) => props.theme.space.lg}; /* 32px */

@media (max-width: ${(props) => props.theme.breakpoints.md}) {
flex-direction: column;
margin-bottom: ${(props) => props.theme.space.xl}; /* 40px */
grid-template-columns: 1fr;
grid-template-areas:
"left"
"right"
"approverActions"
"clarification";
margin-bottom: ${(props) => props.theme.space.xl};
}
`;

Expand All @@ -28,7 +42,7 @@ const LoadingContainer = styled.div`
`;

const LeftColumn = styled.div`
flex: 2;
grid-area: left;

& > *:first-child {
margin-bottom: ${(props) => props.theme.space.base * 4}px; /* 16px */
Expand All @@ -44,30 +58,18 @@ const LeftColumn = styled.div`
`;

const RightColumn = styled.div`
flex: 1;
margin-inline-start: ${(props) => props.theme.space.base * 6}px; /* 24px */

@media (max-width: ${(props) => props.theme.breakpoints.md}) {
margin-inline-start: 0;
}
grid-area: right;
`;

const ApproverActionsInLeft = styled.div`
display: block;
margin-top: ${(props) => props.theme.space.lg};

@media (max-width: ${(props) => props.theme.breakpoints.md}) {
display: none;
}
const ClarificationArea = styled.div`
border-top: 1px solid ${(props) => getColor("grey", 200, props.theme)}; //#E9EBED
grid-area: clarification;
padding-top: ${(props) => props.theme.space.lg};
`;

const ApproverActionsBelowContainer = styled.div`
display: none;
const ApproverActionsWrapper = styled.div`
grid-area: approverActions;
margin-top: ${(props) => props.theme.space.lg};

@media (max-width: ${(props) => props.theme.breakpoints.md}) {
display: block;
}
`;

export interface ApprovalRequestPageProps {
Expand Down Expand Up @@ -110,30 +112,71 @@ function ApprovalRequestPage({
userId === approvalRequest?.assignee_user?.id &&
approvalRequest?.status === "active";

// TODO: replace with arturo check
const hasApprovalClaricationFeature = false;

const mockApprovalClarificationFlowMessages: ApprovalClarificationFlowMessage[] =
[
{
id: "msg1",
author: {
id: "user1",
email: "[email protected]",
avatar: "",
name: "Alice Johnson",
},
message: "Can you clarify the budget allocation for this approval?",
createdAt: "2025-10-01T10:15:00Z",
},
{
id: "msg2",
author: {
id: "user2",
email: null,
avatar: "https://i.pravatar.cc/150?img=2",
name: "Bob Smith",
},
message: "Approved, pending confirmation from finance.",
createdAt: "2025-10-01T10:18:30Z",
},
{
id: "msg3",
author: {
id: "user3",
email: "[email protected]",
avatar: "https://i.pravatar.cc/150?img=3",
name: "Carol Lee",
},
message: "Finance has confirmed the allocations; please proceed.",
createdAt: "2025-10-01T11:00:00Z",
},
{
id: "msg4",
author: {
id: "user1",
email: "[email protected]",
avatar: "https://i.pravatar.cc/150?img=1",
name: "Alice Johnson",
},
message: "Thanks for the clarification! Moving forward with approval.",
createdAt: "2025-10-01T11:05:00Z",
},
];

return (
<>
<ApprovalRequestBreadcrumbs
helpCenterPath={helpCenterPath}
organizations={organizations}
/>

<Container>
<LeftColumn>
<XXL isBold>{approvalRequest?.subject}</XXL>
<MD>{approvalRequest?.message}</MD>
{approvalRequest?.ticket_details && (
<ApprovalTicketDetails ticket={approvalRequest.ticket_details} />
)}
{/* ApproverActions inside LeftColumn, shown on desktop */}
{showApproverActions && (
<ApproverActionsInLeft>
<ApproverActions
approvalWorkflowInstanceId={approvalWorkflowInstanceId}
approvalRequestId={approvalRequestId}
setApprovalRequest={setApprovalRequest}
assigneeUser={approvalRequest?.assignee_user}
/>
</ApproverActionsInLeft>
)}
</LeftColumn>

<RightColumn>
Expand All @@ -144,19 +187,29 @@ function ApprovalRequestPage({
/>
)}
</RightColumn>
</Container>

{/* ApproverActions below Container, shown on mobile/tablet */}
{showApproverActions && (
<ApproverActionsBelowContainer>
<ApproverActions
approvalWorkflowInstanceId={approvalWorkflowInstanceId}
approvalRequestId={approvalRequestId}
setApprovalRequest={setApprovalRequest}
assigneeUser={approvalRequest?.assignee_user}
/>
</ApproverActionsBelowContainer>
)}
{showApproverActions && (
<ApproverActionsWrapper>
<ApproverActions
approvalWorkflowInstanceId={approvalWorkflowInstanceId}
approvalRequestId={approvalRequestId}
setApprovalRequest={setApprovalRequest}
assigneeUser={approvalRequest?.assignee_user}
/>
</ApproverActionsWrapper>
)}

{hasApprovalClaricationFeature && approvalRequest && (
<ClarificationArea>
<ClarificationContainer
approvalRequestId={approvalRequest.id}
status={approvalRequest.status}
baseLocale={baseLocale}
clarificationFlowMessages={mockApprovalClarificationFlowMessages}
/>
</ClarificationArea>
)}
</Container>
</>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import type React from "react";
import { useRef, memo } from "react";
import { Grid, Col, Row } from "@zendeskgarden/react-grid";
import { Avatar } from "@zendeskgarden/react-avatars";
import styled from "styled-components";
import { type ApprovalClarificationFlowMessage } from "../../../types";
import { useIntersectionObserver } from "./hooks/useIntersectionObserver";
import { formatApprovalRequestDate } from "../../../utils";
import UserIcon from "@zendeskgarden/svg-icons/src/16/user-solo-stroke.svg";

export const MessageContainer = styled.div`
margin-top: ${({ theme }) => theme.space.sm};
`;

export const Body = styled.div`
margin-top: ${({ theme }) => theme.space.xs};
`;

export const Timestamp = styled.div`
font-size: ${({ theme }) => theme.fontSizes.sm};
`;

const AvatarCol = styled(Col)`
max-width: 55px;
`;

const DetailsCol = styled(Col)`
width: 272px;
`;

const TimestampCol = styled(Col)`
font-size: ${({ theme }) => theme.fontSizes.sm};
`;

export interface ClarificationCommentProps {
baseLocale: string;
comment: ApprovalClarificationFlowMessage;
commentKey: string;
children?: React.ReactNode;
markCommentAsVisible: (commentKey: string) => void;
}

function ClarificationCommentComponent({
baseLocale,
children,
comment,
commentKey,
markCommentAsVisible,
}: ClarificationCommentProps) {
const containerRef = useRef<HTMLDivElement | null>(null);
const { author, createdAt } = comment;
const { avatar, name } = author;

useIntersectionObserver(containerRef, () => markCommentAsVisible(commentKey));

return (
<MessageContainer ref={containerRef}>
<Grid gutters={false}>
<Row>
<AvatarCol>
<Avatar size="small">
{avatar ? (
<img alt={name} src={avatar} />
) : (
// eslint-disable-next-line @shopify/jsx-no-hardcoded-content
<UserIcon role="img" aria-label="icon avatar" />
)}
</Avatar>
</AvatarCol>

<DetailsCol>
<Row>
<Col alignSelf="stretch">
<strong>{name}</strong>
</Col>
<TimestampCol alignSelf="end" textAlign="end">
{createdAt && (
<Timestamp>
{formatApprovalRequestDate(createdAt, baseLocale)}
</Timestamp>
)}
</TimestampCol>
</Row>

<Body>{children}</Body>
</DetailsCol>
</Row>
</Grid>
</MessageContainer>
);
}

const ClarificationComment = memo(ClarificationCommentComponent);

export default ClarificationComment;
Loading
Loading