Skip to content

Conversation

@AmishaBisht
Copy link
Collaborator

@AmishaBisht AmishaBisht commented Jan 6, 2026

Summary

Test Plan

Summary by CodeRabbit

Release Notes

  • New Features

    • Trial users see an onboarding video modal on first login
    • New trial registration page with OTP verification for account creation
    • Trial expiration countdown banner displays when 7 days or fewer remain, with options to contact support or schedule a call
  • Bug Fixes

    • Updated authentication token handling for improved session management

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 6, 2026

Walkthrough

This PR introduces a comprehensive trial user experience with three primary features: a TrialVideoModal that displays onboarding content for first-time trial users, a TrialRegistration component implementing a two-step signup flow with OTP verification and account allocation, and a TrialExpiryBanner that displays expiration warnings when 7 days or fewer remain. Supporting changes include updates to the Auth component to support trial registration mode with external loading state, modifications to AuthService to persist and retrieve trial-related session data, new API endpoint constants, routing for the trial registration page, CSP updates for Canva iframe embedding, and extensive CSS modules and test coverage for all new components.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • PR #3703: Overlaps substantially with trial feature files (TrialVideoModal, TrialRegistration, TrialBanner components, AuthService session handling, config constants, and CSP updates)
  • PR #3699: Implements the same trial registration feature with TrialRegistration component, route, and trial API endpoint constants
  • PR #3506: Modifies the Auth component (same file affected here with new trial registration mode and loading prop handling)

Suggested labels

status: ready for review

Suggested reviewers

  • akanshaaa19
  • shijithkjayan
  • priyanshu6238

Poem

🐰 A modal hops in, first-time trial delight,
Registration flows smooth through the night,
Banners warn when expiry draws near,
Sessions remember through code crystal clear,
Trial users welcome, the journey's just right! 🎥✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Trial Accounts' directly aligns with the primary objectives: implementing trial account registration, trial expiry banners, and intro videos for trial users.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@AmishaBisht AmishaBisht marked this pull request as draft January 6, 2026 04:23
@github-actions
Copy link

github-actions bot commented Jan 6, 2026

@github-actions github-actions bot temporarily deployed to pull request January 6, 2026 04:25 Inactive
@cypress
Copy link

cypress bot commented Jan 6, 2026

Glific    Run #7041

Run Properties:  status check passed Passed #7041  •  git commit 1eaa29a4da ℹ️: Merge ea3dd40737038c5b4854a415a49347587554ef09 into c7976ea5d9c27c190849e98f8c95...
Project Glific
Branch Review trial-account
Run status status check passed Passed #7041
Run duration 27m 22s
Commit git commit 1eaa29a4da ℹ️: Merge ea3dd40737038c5b4854a415a49347587554ef09 into c7976ea5d9c27c190849e98f8c95...
Committer Amisha Bisht
View all properties for this run ↗︎

Test results
Tests that failed  Failures 0
Tests that were flaky  Flaky 2
Tests that did not run due to a developer annotating a test with .skip  Pending 0
Tests that did not run due to a failure in a mocha hook  Skipped 0
Tests that passed  Passing 191
View all changes introduced in this branch ↗︎

@github-actions github-actions bot temporarily deployed to pull request January 15, 2026 10:42 Inactive
@codecov
Copy link

codecov bot commented Jan 15, 2026

Codecov Report

❌ Patch coverage is 79.05405% with 31 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.82%. Comparing base (c7976ea) to head (ea3dd40).

Files with missing lines Patch % Lines
...ganization/TrialRegistration/TrialRegistration.tsx 70.42% 15 Missing and 6 partials ⚠️
src/containers/TrialBanner/TrialExpiryBanner.tsx 87.87% 4 Missing ⚠️
src/services/AuthService.tsx 50.00% 4 Missing ⚠️
src/App.tsx 66.66% 0 Missing and 1 partial ⚠️
.../components/UI/TrialVideoModal/TrialVideoModal.tsx 92.85% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3710      +/-   ##
==========================================
- Coverage   82.84%   82.82%   -0.02%     
==========================================
  Files         317      320       +3     
  Lines       11854    11994     +140     
  Branches     2486     2524      +38     
==========================================
+ Hits         9820     9934     +114     
- Misses       1280     1300      +20     
- Partials      754      760       +6     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions github-actions bot temporarily deployed to pull request January 16, 2026 09:26 Inactive
@github-actions github-actions bot temporarily deployed to pull request January 18, 2026 19:09 Inactive
@github-actions github-actions bot temporarily deployed to pull request January 20, 2026 18:58 Inactive
@AmishaBisht AmishaBisht marked this pull request as ready for review January 22, 2026 05:15
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/App.tsx (1)

13-53: Move sessionData lookup into the component to avoid stale auth state.
sessionData is computed once at module load (Line 14). If a user logs in after initial load, sessionData stays null, so TrialVideoModal won’t render even though isAuthenticated becomes true. This effectively breaks the feature for normal login flow.

🐛 Proposed fix
-import TrialVideoModal from 'components/UI/TrialVideoModal/TrialVideoModal';
-const sessionData = getAuthSession('session');
+import TrialVideoModal from 'components/UI/TrialVideoModal/TrialVideoModal';

 const App = () => {
   const navigate = useNavigate();
   const [drawerOpen, setDrawerOpen] = useState(true);
 
-  const isAuthenticated = !!getAuthSession('access_token');
+  const isAuthenticated = !!getAuthSession('access_token');
+  const sessionData = getAuthSession('session');
🤖 Fix all issues with AI agents
In `@src/components/UI/TrialVideoModal/TrialVideoModal.tsx`:
- Around line 35-44: The Dialog in the TrialVideoModal component sets
aria-describedby="trial-welcome-description" but no matching element exists;
either remove the aria-describedby prop from the Dialog or add a description
element (e.g., a visually-hidden <div> or <p>) with
id="trial-welcome-description" inside the TrialVideoModal so the reference
points to an actual DOM node (update the JSX near the Dialog return where
showModal, handleClose and styles.dialog are used).

In `@src/containers/Organization/TrialRegistration/TrialRegistration.tsx`:
- Around line 117-124: The handleResendOTP function currently accepts a captcha
token parameter but never uses it; update handleResendOTP to forward the token
to sendOTP (e.g., call sendOTP(formValues, token)) and update the sendOTP
function signature/implementation to accept the token and include it in the API
payload used to resend the OTP. If captcha validation is not required for
resends, remove the token parameter from handleResendOTP and the Captcha
integration to avoid dead params. Ensure you update all references to sendOTP
and handleResendOTP to match the new signature.

In `@src/containers/TrialBanner/TrialExpiryBanner.test.tsx`:
- Around line 8-25: The test saves originalWindowOpen and originalLocationHref
but replaces the entire window.location object, which can break restoration;
capture the original window.location descriptor (e.g., via
Object.getOwnPropertyDescriptor) before modifying in beforeEach, then in
afterEach restore window.open from originalWindowOpen and restore
window.location using the saved descriptor (instead of only setting href) so the
original behavior is fully reinstated; update beforeEach/afterEach around
mockWindowOpen, originalWindowOpen, originalLocationHref and the location
replacement to use descriptor save/restore.
🧹 Nitpick comments (11)
src/components/UI/Layout/Layout.tsx (1)

17-21: Consider memoizing trialSessionData to avoid unnecessary re-renders.

The trialSessionData object is recreated on every render with a new reference, which may cause unnecessary re-renders in TrialVideoModal if it relies on reference equality for props or uses the object in effect dependencies.

Additionally, getAuthSession parses localStorage JSON each time it's called. Consolidating into a single parse would be slightly more efficient.

♻️ Suggested improvement using useMemo
-import { useContext } from 'react';
+import { useContext, useMemo } from 'react';
 import MenuIcon from '@mui/icons-material/Menu';
 import { SideDrawerContext } from 'context/session';
 import GlificLogo from 'assets/images/logo/Logo.svg';
 import { getAuthSession } from 'services/AuthService';
 import TrialVideoModal from 'components/UI/TrialVideoModal/TrialVideoModal';
 import { SideDrawer } from './Navigation/SideDrawer/SideDrawer';
 import styles from './Layout.module.css';

 export const Layout = ({ children }: LayoutProps) => {
   const { setDrawerOpen } = useContext(SideDrawerContext);

-  const trialSessionData = {
-    last_login_time: getAuthSession('last_login_time'),
-    is_trial: getAuthSession('is_trial'),
-    trial_expiration_date: getAuthSession('trial_expiration_date'),
-  };
+  const trialSessionData = useMemo(() => {
+    const session = getAuthSession('session');
+    const parsed = session ? JSON.parse(session) : {};
+    return {
+      last_login_time: parsed.last_login_time,
+      is_trial: parsed.is_trial,
+      trial_expiration_date: parsed.trial_expiration_date,
+    };
+  }, []);
src/containers/Organization/TrialRegistration/TrialRegistration.test.tsx (1)

1-246: Refactor to use userEvent to align with established codebase patterns.

The @testing-library/user-event dependency is already available (^14.6.1) and widely used across the codebase. Similar auth test files (Registration.test.tsx, Login.test.tsx) already use userEvent with the userEvent.setup() pattern, which simulates real user interactions more accurately than fireEvent.

Replace fireEvent.change() calls with userEvent.type() and fireEvent.click() with userEvent.click(). This will reduce timing edge cases and keep the test suite consistent with the rest of the project.

♻️ Example adjustment
-import { fireEvent, render, screen, waitFor } from '@testing-library/react';
+import { render, screen, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
...
-    fireEvent.change(screen.getByPlaceholderText('Organization Name'), {
-      target: { value: 'Testing Org' },
-    });
+    const user = userEvent.setup();
+    await user.type(screen.getByPlaceholderText('Organization Name'), 'Testing Org');
src/components/UI/TrialVideoModal/TrialVideoModal.tsx (1)

54-61: Consider extracting hardcoded Canva URL to configuration.

The iframe source URL is hardcoded. If this video needs to be updated or differs per environment, consider moving it to a config constant.

src/containers/Organization/TrialRegistration/TrialRegistration.tsx (3)

181-181: Avoid using Array<any> type.

The formFields array is typed as Array<any>, losing type safety. Consider defining a proper interface for form field configuration.

♻️ Proposed type definition
interface FormFieldConfig {
  component: React.ComponentType<any>;
  name: keyof TrialFormValues;
  type: string;
  placeholder: string;
  styles?: string;
  disabled?: boolean;
  autoComplete?: string;
  helperText?: string;
  endAdornment?: React.ReactNode;
}

const formFields: FormFieldConfig[] = [
  // ...
];

76-101: Inconsistent API response handling pattern.

The success check at line 79 (data.data?.message || (!data.error && data.data)) differs from the check at line 149 (data.success && data.data?.login_url). Consider aligning response handling or documenting the expected response structures for each endpoint.


52-65: Dynamic validation schema may cause unnecessary re-renders.

The FormSchema is recreated on every render due to the otpSent dependency in the OTP validation rule. Consider memoizing the schema or restructuring to avoid recreation.

♻️ Proposed optimization
import { useMemo } from 'react';

// Inside component:
const FormSchema = useMemo(() => Yup.object().shape({
  organizationName: Yup.string()
    .required('Organization name is required')
    .matches(/^[A-Za-z\s]+$/, 'Organization name can only contain alphabets and spaces')
    .min(5, 'Organization must be at least 5 characters'),
  // ... other fields
  otp: otpSent ? Yup.string().required('OTP is required') : Yup.string(),
}), [otpSent, t]);
src/components/UI/TrialVideoModal/TrialVideoModal.test.tsx (1)

5-9: Consider sharing the SessionData interface.

The SessionData interface is duplicated from TrialVideoModal.tsx. Consider exporting it from the component file or a shared types module to maintain a single source of truth.

src/containers/TrialBanner/TrialExpiryBanner.module.css (1)

112-119: Consider adding text truncation fallback for very narrow containers.

The BannerSubtext uses text-overflow: ellipsis which is good, but if the parent container has unusual sizing, the text might not truncate as expected. This is a minor consideration given the banner is typically in a sidebar.

src/containers/Auth/Auth.module.css (1)

361-406: Consider MUI v7 recommended patterns for input styling.

While .MuiInputBase-root and .MuiOutlinedInput-root are stable documented class names, MUI v7 officially recommends using the slots/slotProps API, the sx prop, or theme component overrides instead of direct CSS class selectors for long-term maintainability and consistency with the standardized customization pattern.

src/containers/TrialBanner/TrialExpiryBanner.tsx (2)

33-39: Consider extracting hardcoded URLs to constants.

The support email and calendar URL are hardcoded. For maintainability, consider moving these to a constants file or configuration, especially if they might be reused or need environment-specific values.

Example extraction
// In common/constants.ts or similar
export const TRIAL_SUPPORT_EMAIL = 'connect@glific.org';
export const TRIAL_SCHEDULE_CALL_URL = 'https://calendar.app.google/USJMfhSsDvW5yS6B7';

97-114: Consider using the custom Button component for consistency.

The action buttons use MUI's Button directly, while other parts of the codebase use the custom Button wrapper from components/UI/Form/Button/Button. For consistency across the application, consider using the custom component.

Suggested change
-import { Button, Collapse, IconButton } from '@mui/material';
+import { Collapse, IconButton } from '@mui/material';
+import { Button } from 'components/UI/Form/Button/Button';

Comment on lines +35 to +44
return (
<Dialog
open={showModal}
onClose={handleClose}
maxWidth="md"
fullWidth
className={styles.dialog}
aria-labelledby="trial-welcome-title"
aria-describedby="trial-welcome-description"
>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing element for aria-describedby reference.

The Dialog specifies aria-describedby="trial-welcome-description" but no element with id="trial-welcome-description" exists in the component. Either remove the attribute or add a description element.

🔧 Proposed fix
     <Dialog
       open={showModal}
       onClose={handleClose}
       maxWidth="md"
       fullWidth
       className={styles.dialog}
       aria-labelledby="trial-welcome-title"
-      aria-describedby="trial-welcome-description"
     >
+      <DialogTitle id="trial-welcome-title" className={styles.visuallyHidden}>
+        Welcome to Glific Trial
+      </DialogTitle>
       <IconButton aria-label="Close trial welcome video" onClick={handleClose} className={styles.closeButton}>

Alternatively, add a visually hidden description element with id="trial-welcome-description".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return (
<Dialog
open={showModal}
onClose={handleClose}
maxWidth="md"
fullWidth
className={styles.dialog}
aria-labelledby="trial-welcome-title"
aria-describedby="trial-welcome-description"
>
return (
<Dialog
open={showModal}
onClose={handleClose}
maxWidth="md"
fullWidth
className={styles.dialog}
aria-labelledby="trial-welcome-title"
>
<DialogTitle id="trial-welcome-title" className={styles.visuallyHidden}>
Welcome to Glific Trial
</DialogTitle>
🤖 Prompt for AI Agents
In `@src/components/UI/TrialVideoModal/TrialVideoModal.tsx` around lines 35 - 44,
The Dialog in the TrialVideoModal component sets
aria-describedby="trial-welcome-description" but no matching element exists;
either remove the aria-describedby prop from the Dialog or add a description
element (e.g., a visually-hidden <div> or <p>) with
id="trial-welcome-description" inside the TrialVideoModal so the reference
points to an actual DOM node (update the JSX near the Dialog return where
showModal, handleClose and styles.dialog are used).

Comment on lines +117 to +124
const handleResendOTP = async (token: string) => {
if (!formValues) {
return;
}

setAuthError('');
await sendOTP(formValues);
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Unused captcha token in handleResendOTP.

The token parameter received from the Captcha component is never used in the sendOTP call. If captcha verification is required for resending OTP, the token should be included in the API payload. Otherwise, the captcha serves no protective purpose.

🔧 Proposed fix if captcha should be validated
-  const handleResendOTP = async (token: string) => {
+  const handleResendOTP = async (captchaToken: string | null) => {
     if (!formValues) {
       return;
     }
+    if (!captchaToken) {
+      setAuthError('Captcha verification failed. Please try again.');
+      return;
+    }

     setAuthError('');
-    await sendOTP(formValues);
+    await sendOTP(formValues, captchaToken);
   };

Then update sendOTP to accept and include the token in the payload.

🤖 Prompt for AI Agents
In `@src/containers/Organization/TrialRegistration/TrialRegistration.tsx` around
lines 117 - 124, The handleResendOTP function currently accepts a captcha token
parameter but never uses it; update handleResendOTP to forward the token to
sendOTP (e.g., call sendOTP(formValues, token)) and update the sendOTP function
signature/implementation to accept the token and include it in the API payload
used to resend the OTP. If captcha validation is not required for resends,
remove the token parameter from handleResendOTP and the Captcha integration to
avoid dead params. Ensure you update all references to sendOTP and
handleResendOTP to match the new signature.

Comment on lines +8 to +25
const originalWindowOpen = window.open;
const originalLocationHref = window.location.href;

let mockWindowOpen: typeof window.open;

beforeEach(() => {
mockWindowOpen = vi.fn();
window.open = mockWindowOpen;

delete (window as unknown as { location: unknown }).location;
(window as unknown as { location: { href: string } }).location = { href: '' };
});

afterEach(() => {
window.open = originalWindowOpen;
(window as unknown as { location: { href: string } }).location = { href: originalLocationHref };
vi.clearAllMocks();
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Window location restoration may not work correctly.

The originalLocationHref captures the initial href string value, but in afterEach you're assigning it back after replacing the entire location object. This may not properly restore the original location behavior in subsequent tests.

🔧 Suggested improvement
-  const originalWindowOpen = window.open;
-  const originalLocationHref = window.location.href;
-
-  let mockWindowOpen: typeof window.open;
+  let mockWindowOpen: ReturnType<typeof vi.fn>;
+  let originalLocation: Location;

   beforeEach(() => {
     mockWindowOpen = vi.fn();
     window.open = mockWindowOpen;
-
-    delete (window as unknown as { location: unknown }).location;
-    (window as unknown as { location: { href: string } }).location = { href: '' };
+    originalLocation = window.location;
+    Object.defineProperty(window, 'location', {
+      value: { href: '' },
+      writable: true,
+    });
   });

   afterEach(() => {
-    window.open = originalWindowOpen;
-    (window as unknown as { location: { href: string } }).location = { href: originalLocationHref };
+    window.open = originalLocation.constructor.prototype.open;
+    Object.defineProperty(window, 'location', {
+      value: originalLocation,
+      writable: true,
+    });
     vi.clearAllMocks();
   });
🤖 Prompt for AI Agents
In `@src/containers/TrialBanner/TrialExpiryBanner.test.tsx` around lines 8 - 25,
The test saves originalWindowOpen and originalLocationHref but replaces the
entire window.location object, which can break restoration; capture the original
window.location descriptor (e.g., via Object.getOwnPropertyDescriptor) before
modifying in beforeEach, then in afterEach restore window.open from
originalWindowOpen and restore window.location using the saved descriptor
(instead of only setting href) so the original behavior is fully reinstated;
update beforeEach/afterEach around mockWindowOpen, originalWindowOpen,
originalLocationHref and the location replacement to use descriptor
save/restore.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants