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
111 changes: 111 additions & 0 deletions echo/frontend/src/components/common/FeedbackPortalModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { t } from "@lingui/core/macro";
import { Trans } from "@lingui/react/macro";
import {
Anchor,
Button,
Divider,
Group,
Modal,
rem,
Stack,
Text,
} from "@mantine/core";
import { getProductFeedbackUrl } from "@/config";
import { QRCode } from "./QRCode";

interface FeedbackPortalModalProps {
opened: boolean;
onClose: () => void;
locale?: string;
}

export const FeedbackPortalModal = ({
opened,
onClose,
locale,
}: FeedbackPortalModalProps) => {
const feedbackUrl = getProductFeedbackUrl(locale);

const actionButtonStyles = {
root: {
minHeight: rem(40),
paddingBottom: rem(10),
paddingLeft: rem(20),
paddingRight: rem(20),
paddingTop: rem(10),
},
} as const;

return (
<Modal
opened={opened}
onClose={onClose}
title={t`Feedback portal`}
centered
>
<Stack gap="lg">
<Stack gap="md">
<Text size="sm">
<Trans>
We'd love to hear from you. Whether you have an idea for something
new, you've hit a bug, spotted a translation that feels off, or
just want to share how things have been going.
</Trans>
</Text>
<Text size="sm">
<Trans>
To help us act on it, try to include where it happened and what
you were trying to do. For bugs, tell us what went wrong. For
ideas, tell us what need it would solve for you.
</Trans>
</Text>
<Text size="sm">
<Trans>
Just talk or type naturally. Your input goes directly to our
product team and genuinely helps us make dembrane better. We read
everything.
</Trans>
</Text>
Comment on lines +48 to +68
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Trim the modal copy.

This is a lot of text for a single decision point, and it reads heavier than the rest of the app. I'd compress this to one short intro plus one short “what to include” hint. As per coding guidelines "use shortest possible copy with highest clarity, avoid jargon and corporate speak, write like explaining to a colleague".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@echo/frontend/src/components/common/FeedbackPortalModal.tsx` around lines 48
- 68, In FeedbackPortalModal, replace the three verbose <Text size="sm"> blocks
with two concise lines: one short intro and one brief "what to include" hint;
update the JSX inside the FeedbackPortalModal component (where the current <Text
size="sm">... blocks are) to render a single sentence like "Tell us what
happened or what you'd like to see" and a second sentence like "Include where it
happened and one key detail (steps, error, or expected outcome)"; keep the
<Trans> wrapper around both for i18n and preserve size="sm" and surrounding
layout.

</Stack>

<Group align="center" gap="lg" wrap="nowrap">
<QRCode
value={feedbackUrl}
href={feedbackUrl}
className="h-auto w-full min-w-[80px] max-w-[128px]"
/>
<Stack gap={4}>
<Text fw={600}>
<Trans>Scan or click to open the feedback portal</Trans>
</Text>
<Group gap="xs">
<Text size="xs" c="dimmed">
<Trans>Or prefer to chat directly?</Trans>
</Text>
<Anchor
href="https://cal.com/sameer-dembrane"
target="_blank"
size="xs"
>
<Trans>Book a call with us</Trans>
</Anchor>
</Group>
</Stack>
</Group>

<Divider />

<Group justify="flex-end" gap="sm" align="center">
<Button
variant="default"
size="md"
onClick={onClose}
styles={actionButtonStyles}
>
<Trans>Cancel</Trans>
</Button>
</Group>
</Stack>
</Modal>
);
};
70 changes: 62 additions & 8 deletions echo/frontend/src/components/common/QRCode.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,32 @@
import { rem } from "@mantine/core";
import { IconExternalLink } from "@tabler/icons-react";
import { type CSSProperties, type Ref, useState } from "react";
import { QRCode as Q } from "react-qrcode-logo";

import { CURRENT_BRAND } from "./Logo";

/**
* QRCode component
* Try to wrap this component in a div with a fixed width and height
*/
export const QRCode = (props: { value: string; ref?: any }) => {
return (
interface QRCodeProps {
value: string;
href?: string;
ref?: Ref<HTMLDivElement>;
className?: string;
style?: CSSProperties;
"data-testid"?: string;
}

export const QRCode = ({
value,
href,
ref,
className,
style,
"data-testid": dataTestId,
}: QRCodeProps) => {
const [hovered, setHovered] = useState(false);

const qrElement = (
<Q
value={props.value}
ref={props.ref}
value={value}
logoImage={
CURRENT_BRAND === "dembrane"
? "/dembrane-logomark-cropped.png"
Expand All @@ -29,4 +45,42 @@ export const QRCode = (props: { value: string; ref?: any }) => {
}}
/>
);

if (!href) {
return (
<div ref={ref} className={className} style={style} data-testid={dataTestId}>
{qrElement}
</div>
);
}

return (
<a
ref={ref as Ref<HTMLAnchorElement>}
href={href}
target="_blank"
rel="noopener noreferrer"
className={`relative block cursor-pointer overflow-hidden rounded-lg bg-white transition-all ${className ?? ""}`}
style={style}
data-testid={dataTestId}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
{qrElement}
<div
className="absolute inset-0 flex items-center justify-center rounded-lg transition-all print:hidden"
style={{
backgroundColor: hovered
? "rgba(65, 105, 225, 0.85)"
: "transparent",
Comment on lines +63 to +75
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Replace hardcoded QR colors with theme tokens/CSS variables.

bg-white, rgba(65, 105, 225, 0.85), and color="white" hardcode palette values in a shared component. Please switch these to the project’s color tokens / CSS variables so theme and whitelabel behavior stay consistent.

As per coding guidelines: echo/**/*.{ts,tsx,css,scss}: Use color tokens from brand/colors.json for programmatic use instead of hardcoded color values and echo/frontend/**/*.{ts,tsx,css}: Use CSS variables var(--app-background) and var(--app-text) instead of hardcoded colors like #F6F4F1or#2D2D2C``.

Also applies to: 81-81

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@echo/frontend/src/components/common/QRCode.tsx` around lines 63 - 75, The
QRCode component currently uses hardcoded colors (className includes bg-white,
the hover background rgba(65,105,225,0.85), and a color="white" further down)
which breaks theming; update the QRCode.tsx component to replace bg-white with
the app background token/variable (e.g., var(--app-background) or the
corresponding brand/colors.json token), replace the hover inline style rgba(...)
with the theme/brand primary token or CSS variable (and preserve opacity via
rgba from token or use HSLA/CSS opacity), and replace color="white" with
var(--app-text) or the appropriate brand text token; ensure changes touch the
JSX elements that use hovered state, className and the element rendering
qrElement so the component uses CSS variables/brand tokens instead of hardcoded
values and remains print-hidden as before.

opacity: hovered ? 1 : 0,
}}
>
<IconExternalLink
style={{ height: rem(32), width: rem(32) }}
color="white"
/>
</div>
</a>
);
};
105 changes: 9 additions & 96 deletions echo/frontend/src/components/layout/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,11 @@ import { t } from "@lingui/core/macro";
import { Trans } from "@lingui/react/macro";
import {
ActionIcon,
Anchor,
Badge,
Box,
Button,
Group,
Menu,
Modal,
Paper,
Stack,
Text,
} from "@mantine/core";
import * as Sentry from "@sentry/react";
Expand All @@ -35,7 +31,6 @@ import {
COMMUNITY_SLACK_URL,
DIRECTUS_PUBLIC_URL,
ENABLE_ANNOUNCEMENTS,
getProductFeedbackUrl,
} from "@/config";
import { useI18nNavigate } from "@/hooks/useI18nNavigate";
import { useWhitelabelLogo } from "@/hooks/useWhitelabelLogo";
Expand All @@ -45,6 +40,7 @@ import { testId } from "@/lib/testUtils";
import { AnnouncementIcon } from "../announcement/AnnouncementIcon";
import { Announcements } from "../announcement/Announcements";
import { TopAnnouncementBar } from "../announcement/TopAnnouncementBar";
import { FeedbackPortalModal } from "../common/FeedbackPortalModal";
import { Logo } from "../common/Logo";
import { UserAvatar } from "../common/UserAvatar";
import { LanguagePicker } from "../language/LanguagePicker";
Expand Down Expand Up @@ -286,97 +282,14 @@ const HeaderView = ({ isAuthenticated, loading }: HeaderViewProps) => {
</Group>
</Paper>

<Modal
opened={feedbackFallbackOpen}
onClose={() => setFeedbackFallbackOpen(false)}
title={t`Report an issue`}
centered
>
<Stack gap="md">
<Text size="sm">
<Trans>
The built-in issue reporter could not be loaded. You can still let
us know what went wrong through our feedback portal. It helps us
fix things faster than not submitting a report.
</Trans>
</Text>
<Group justify="flex-end">
<Button
variant="default"
onClick={() => setFeedbackFallbackOpen(false)}
>
<Trans>Cancel</Trans>
</Button>
<Button
component="a"
href={getProductFeedbackUrl(language)}
target="_blank"
onClick={() => setFeedbackFallbackOpen(false)}
>
<Trans>Go to feedback portal</Trans>
</Button>
</Group>
</Stack>
</Modal>

<Modal
opened={feedbackPortalOpen}
onClose={() => setFeedbackPortalOpen(false)}
title={t`Feedback portal`}
centered
>
<Stack gap="md">
<Text size="sm">
<Trans>
We'd love to hear from you. Whether you have an idea for something
new, you've hit a bug, spotted a translation that feels off, or
just want to share how things have been going.
</Trans>
</Text>
<Text size="sm">
<Trans>
To help us act on it, try to include where it happened and what
you were trying to do. For bugs, tell us what went wrong. For
ideas, tell us what need it would solve for you.
</Trans>
</Text>
<Text size="sm">
<Trans>
Just talk or type naturally. Your input goes directly to our
product team and genuinely helps us make dembrane better. We read
everything.
</Trans>
</Text>
<Text size="sm" c="dimmed">
<Trans>
Prefer to chat directly?{" "}
<Anchor
href="https://cal.com/sameer-dembrane"
target="_blank"
size="sm"
>
Book a call with me
</Anchor>
</Trans>
</Text>
<Group justify="flex-end">
<Button
variant="default"
onClick={() => setFeedbackPortalOpen(false)}
>
<Trans>Cancel</Trans>
</Button>
<Button
component="a"
href={getProductFeedbackUrl(language)}
target="_blank"
onClick={() => setFeedbackPortalOpen(false)}
>
<Trans>Open feedback portal</Trans>
</Button>
</Group>
</Stack>
</Modal>
<FeedbackPortalModal
opened={feedbackFallbackOpen || feedbackPortalOpen}
onClose={() => {
setFeedbackFallbackOpen(false);
setFeedbackPortalOpen(false);
}}
locale={language}
/>
</>
);
};
Expand Down
34 changes: 6 additions & 28 deletions echo/frontend/src/components/project/ProjectQRCode.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { t } from "@lingui/core/macro";
import { Trans } from "@lingui/react/macro";
import {
Box,
Button,
CopyButton,
Group,
Expand All @@ -14,10 +13,9 @@ import {
IconCheck,
IconCopy,
IconDownload,
IconExternalLink,
IconPresentation,
} from "@tabler/icons-react";
import { useMemo, useRef, useState } from "react";
import { useMemo, useRef } from "react";
import { PARTICIPANT_BASE_URL } from "@/config";
import { useAppPreferences } from "@/hooks/useAppPreferences";
import { testId } from "@/lib/testUtils";
Expand Down Expand Up @@ -81,7 +79,6 @@ export const useProjectSharingLink = (project?: Project) => {

export const ProjectQRCode = ({ project }: ProjectQRCodeProps) => {
const link = useProjectSharingLink(project);
const [qrHovered, setQrHovered] = useState(false);
const qrRef = useRef<HTMLDivElement>(null);

const handleOpenHostGuide = () => {
Expand Down Expand Up @@ -133,32 +130,13 @@ export const ProjectQRCode = ({ project }: ProjectQRCodeProps) => {
>
{project?.is_conversation_allowed ? (
<Group align="center" justify="center" gap="lg">
{/* Interactive QR Code */}
<Box
<QRCode
value={link}
href={link}
ref={qrRef}
className="relative h-auto w-full min-w-[80px] max-w-[128px] cursor-pointer overflow-hidden rounded-lg bg-white transition-all"
onMouseEnter={() => setQrHovered(true)}
onMouseLeave={() => setQrHovered(false)}
onClick={() => window.open(link, "_blank")}
className="h-auto w-full min-w-[80px] max-w-[128px]"
{...testId("project-qr-code")}
>
<QRCode value={link} />
{/* Hover overlay */}
<div
className="absolute inset-0 flex items-center justify-center rounded-lg transition-all"
style={{
backgroundColor: qrHovered
? "rgba(65, 105, 225, 0.85)"
: "transparent",
opacity: qrHovered ? 1 : 0,
}}
>
<IconExternalLink
style={{ height: rem(32), width: rem(32) }}
color="white"
/>
</div>
</Box>
/>
<div className="flex flex-col flex-wrap gap-2">
{showQuickStart && (
<Button
Expand Down
Loading
Loading