Skip to content

feat: new blocks#183

Merged
hmbanan666 merged 1 commit into
mainfrom
webinar-update
Sep 25, 2025
Merged

feat: new blocks#183
hmbanan666 merged 1 commit into
mainfrom
webinar-update

Conversation

@hmbanan666
Copy link
Copy Markdown
Collaborator

@hmbanan666 hmbanan666 commented Sep 25, 2025

Summary by CodeRabbit

  • New Features
    • Added Hero, Header, Countdown, CountdownTimer, and HeroVideo components, including a live countdown to a specific date and a prominent signup CTA.
  • Refactor
    • Reworked homepage to use new Header and Hero components; integrated Countdown section.
  • Style
    • Adjusted HeaderLogo sizing; tweaked InfiniteTitlesDivider visuals (colors/opacity).
  • Improvements
    • InfiniteTitlesDivider now dynamically expands items for a fuller marquee effect.
  • Chores
    • Switched site rendering to prerender in configuration.
    • Removed unnecessary sharp dependency.

@hmbanan666 hmbanan666 self-assigned this Sep 25, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Sep 25, 2025

Walkthrough

Adds new webinar UI components (Hero, Header, Countdown, CountdownTimer, CountdownTimerItem, HeroVideo), updates page assembly to use them, adjusts styles for HeaderLogo and InfiniteTitlesDivider, changes nuxt routeRules to prerender all routes, and removes the sharp dependency.

Changes

Cohort / File(s) Summary
Countdown feature
apps/webinar/app/components/Countdown.vue, apps/webinar/app/components/CountdownTimer.vue, apps/webinar/app/components/CountdownTimerItem.vue
New countdown section with live timer to a fixed date, composed of display items; interval setup/cleanup on mount/unmount.
Hero and Header
apps/webinar/app/components/Hero.vue, apps/webinar/app/components/HeroVideo.vue, apps/webinar/app/components/Header.vue
Added Hero with CTA links and embedded video; new Header with logo, color mode button, and external links.
Page assembly
apps/webinar/app/pages/index.vue
Replaces inline hero with new Header and Hero components; adds Countdown section; removes prior hero links state.
UI tweaks
apps/webinar/app/components/HeaderLogo.vue, apps/webinar/app/components/InfiniteTitlesDivider.vue
HeaderLogo resized and alignment adjusted; InfiniteTitlesDivider styling tweaks and items made reactive with onMounted duplication logic.
Build/runtime config
apps/webinar/nuxt.config.ts
routeRules changed from swr: true to prerender: true for '/**'.
Dependencies
apps/webinar/package.json
Removes dependency on sharp; no other dependency changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Browser
  participant NuxtApp as Nuxt App (Client)
  participant Components as Header/Hero/Countdown
  participant Timer as CountdownTimer (Interval)

  User->>Browser: Navigate to /
  Browser->>NuxtApp: Load app & hydrate
  NuxtApp->>Components: Render Header and Hero
  NuxtApp->>Components: Render Countdown
  Components->>Timer: mount()
  Timer->>Timer: setInterval(1000)
  loop Every 1s
    Timer->>Timer: compute D/H/M/S to 2025-10-25T00:00:00
    Timer-->>Components: update reactive state
    Components-->>Browser: re-render timer values
  end
  note over Timer: clearInterval on unmount
Loading
sequenceDiagram
  autonumber
  participant Dev as Build
  participant Nuxt as Nuxt Prerender
  participant Static as Static Output
  participant CDN as Server/CDN
  participant User as Browser

  Dev->>Nuxt: Build with routeRules '/**': { prerender: true }
  Nuxt->>Static: Generate static HTML/Assets
  Static->>CDN: Deploy files
  User->>CDN: Request page
  CDN-->>User: Serve prerendered HTML
  note over User: Client hydrates Vue components
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • feat: webinar app #181 — Touches the same webinar component paths (HeaderLogo.vue, InfiniteTitlesDivider.vue, pages/index.vue), likely part of the same UI overhaul.

Poem

A rabbit counts the days with cheer,
Tick-tock hops toward a date so near.
Hero shines, the header’s bright,
Prerendered paths load fast and light.
Buttons blink—let’s go, let’s run!
Until the countdown whispers: done. 🐇⏳

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title Check ❓ Inconclusive The title “feat: new blocks” is too generic and does not convey which components or features are introduced, making it unclear for team members scanning the history what the main content of the pull request is. Consider renaming the pull request to something more specific such as “feat: add webinar page blocks including Countdown, Hero, Header, and Timer components” to clearly reflect the primary UI additions.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch webinar-update

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.

@sonarqubecloud
Copy link
Copy Markdown

@hmbanan666 hmbanan666 merged commit dbe5cb5 into main Sep 25, 2025
7 of 8 checks passed
@hmbanan666 hmbanan666 deleted the webinar-update branch September 25, 2025 15:25
Copy link
Copy Markdown

@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: 4

Caution

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

⚠️ Outside diff range comments (1)
apps/webinar/package.json (1)

15-23: Sharp removal is not safe – still used directly and transitively

  • apps/web-app/server/api/**/image.post.ts import and use sharp for image processing
  • apps/core-telegram, apps/web-storefront and apps/geo-vault package.json still list sharp
  • pnpm-workspace.yaml lists sharp under onlyBuiltDependencies
  • packages/ui/nuxt.config.ts includes '@nuxt/image' (which pulls in sharp)
    Retain or replace all these usages before removing sharp.
🧹 Nitpick comments (4)
apps/webinar/app/components/InfiniteTitlesDivider.vue (1)

28-40: Avoid self-referential push causing exponential growth and potential jank.

Mutating the same array while reading from it is harder to reason about and scales 32x items. Use concat to avoid referencing the same array during mutation (keeps behavior but safer).

 onMounted(() => {
   // add items
   for (let i = 0; i < 5; i++) {
-    items.value.push(...items.value)
+    // Avoid reading and writing from the same array reference in one op
+    items.value = items.value.concat(items.value)
   }
 })

Optionally, compute repeats based on viewport width to avoid unnecessary DOM nodes.

apps/webinar/nuxt.config.ts (1)

9-10: Prefer scoping prerender to pages; avoid applying to API/globally.

Prerendering all routes can affect server routes and build time. Scope to pages and explicitly exclude APIs.

-    '/**': { prerender: true },
+    '/': { prerender: true },
+    '/api/**': { prerender: false },

If more pages exist, list them or use nitro.prerender.routes. Verify no auth-related routes are unintentionally prerendered with nuxt-auth-utils.

apps/webinar/app/components/HeroVideo.vue (1)

2-8: Reduce bandwidth and improve mobile behavior for video.

Add preload="metadata" and playsinline. Consider moving the URL to runtime config for easier updates.

   <video
-    controls
+    controls
+    preload="metadata"
+    playsinline
     class="max-h-180 w-auto mx-auto rounded-xl"
   >

Example using runtime config (outside this diff):

<script setup lang="ts">
const { public: { heroVideoUrl } } = useRuntimeConfig()
</script>

<template>
  <video controls preload="metadata" playsinline class="max-h-180 w-auto mx-auto rounded-xl">
    <source :src="heroVideoUrl" type="video/mp4">
    Ваш браузер не поддерживает видео
  </video>
</template>
apps/webinar/app/components/CountdownTimerItem.vue (1)

1-14: LGTM: simple, typed presentational unit.

No issues spotted. If you need zero-padding (e.g., 02), consider formatting upstream or adding an optional pad prop later.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1b7573b and 194c246.

⛔ Files ignored due to path filters (2)
  • apps/webinar/public/sushi-main-logo.png is excluded by !**/*.png
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (11)
  • apps/webinar/app/components/Countdown.vue (1 hunks)
  • apps/webinar/app/components/CountdownTimer.vue (1 hunks)
  • apps/webinar/app/components/CountdownTimerItem.vue (1 hunks)
  • apps/webinar/app/components/Header.vue (1 hunks)
  • apps/webinar/app/components/HeaderLogo.vue (1 hunks)
  • apps/webinar/app/components/Hero.vue (1 hunks)
  • apps/webinar/app/components/HeroVideo.vue (1 hunks)
  • apps/webinar/app/components/InfiniteTitlesDivider.vue (1 hunks)
  • apps/webinar/app/pages/index.vue (2 hunks)
  • apps/webinar/nuxt.config.ts (1 hunks)
  • apps/webinar/package.json (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (3)
apps/webinar/app/components/InfiniteTitlesDivider.vue (1)

14-22: LGTM: visual adjustments improve contrast.

Text color and icon opacity changes look good on the yellow background.

apps/webinar/app/pages/index.vue (1)

2-4: Verify Countdown is SSR-safe under prerender.

If Countdown uses timers/window on setup, wrap with to avoid hydration issues with global prerendering.

If needed:

<ClientOnly>
  <Countdown />
</ClientOnly>

Also applies to: 25-29

apps/webinar/app/components/Countdown.vue (1)

1-32: LGTM!

The countdown block is well composed and aligns with the surrounding UI. No issues spotted.

Comment on lines +21 to +29
const interval = setInterval(() => {
const now = new Date()
const diff = target.value.getTime() - now.getTime()
state.value = {
days: Math.floor(diff / 86400000),
hours: Math.floor((diff % 86400000) / 3600000),
minutes: Math.floor((diff % 3600000) / 60000),
seconds: Math.floor((diff % 60000) / 1000),
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Prevent negative countdown values after the target date.

Once the webinar date passes, diff becomes negative and the UI starts showing negative days/hours/minutes, which is a poor user experience. Clamp the countdown at zero and stop the interval when the date is reached; also compute the first tick immediately so the timer doesn’t sit at all zeros for a full second after mount.

Apply this diff:

+const SECOND = 1000
+const MINUTE = SECOND * 60
+const HOUR = MINUTE * 60
+const DAY = HOUR * 24
+
+const state = ref({
+  days: 0,
+  hours: 0,
+  minutes: 0,
+  seconds: 0,
+})
+
+let interval: ReturnType<typeof setInterval>
+
+const updateCountdown = () => {
+  const diff = target.value.getTime() - Date.now()
+
+  if (diff <= 0) {
+    state.value = { days: 0, hours: 0, minutes: 0, seconds: 0 }
+    clearInterval(interval)
+    return
+  }
+
+  state.value = {
+    days: Math.floor(diff / DAY),
+    hours: Math.floor((diff % DAY) / HOUR),
+    minutes: Math.floor((diff % HOUR) / MINUTE),
+    seconds: Math.floor((diff % MINUTE) / SECOND),
+  }
+}
+
 onMounted(() => {
-  const interval = setInterval(() => {
-    const now = new Date()
-    const diff = target.value.getTime() - now.getTime()
-    state.value = {
-      days: Math.floor(diff / 86400000),
-      hours: Math.floor((diff % 86400000) / 3600000),
-      minutes: Math.floor((diff % 3600000) / 60000),
-      seconds: Math.floor((diff % 60000) / 1000),
-    }
-  }, 1000)
-
-  onBeforeUnmount(() => clearInterval(interval))
+  updateCountdown()
+  interval = setInterval(updateCountdown, 1000)
 })
+
+onBeforeUnmount(() => clearInterval(interval))
📝 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
const interval = setInterval(() => {
const now = new Date()
const diff = target.value.getTime() - now.getTime()
state.value = {
days: Math.floor(diff / 86400000),
hours: Math.floor((diff % 86400000) / 3600000),
minutes: Math.floor((diff % 3600000) / 60000),
seconds: Math.floor((diff % 60000) / 1000),
}
const SECOND = 1000
const MINUTE = SECOND * 60
const HOUR = MINUTE * 60
const DAY = HOUR * 24
const state = ref({
days: 0,
hours: 0,
minutes: 0,
seconds: 0,
})
let interval: ReturnType<typeof setInterval>
const updateCountdown = () => {
const diff = target.value.getTime() - Date.now()
if (diff <= 0) {
state.value = { days: 0, hours: 0, minutes: 0, seconds: 0 }
clearInterval(interval)
return
}
state.value = {
days: Math.floor(diff / DAY),
hours: Math.floor((diff % DAY) / HOUR),
minutes: Math.floor((diff % HOUR) / MINUTE),
seconds: Math.floor((diff % MINUTE) / SECOND),
}
}
onMounted(() => {
updateCountdown()
interval = setInterval(updateCountdown, 1000)
})
onBeforeUnmount(() => clearInterval(interval))
🤖 Prompt for AI Agents
In apps/webinar/app/components/CountdownTimer.vue around lines 21 to 29, the
interval handler lets diff go negative causing negative time values and waits
one second before first tick; fix by computing the tick immediately after
mounting, clamping diff to a minimum of 0 before converting to
days/hours/minutes/seconds, and when diff <= 0 clearInterval(interval) to stop
the timer; update state using the clamped values and ensure the interval
reference is cleared to avoid memory leaks.

Comment on lines +7 to +23
<UColorModeButton size="xl" />
<UButton
to="https://t.me/SLFranchiseBot"
size="xl"
color="neutral"
variant="ghost"
icon="simple-icons:telegram"
/>
<UButton
to="https://vk.com/franshizasushi"
size="xl"
color="neutral"
variant="ghost"
icon="simple-icons:vk"
/>
</div>
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Icon-only external buttons need accessible labels and safe external targets.

Add aria-labels and open external links safely in new tabs.

         <UButton
           to="https://t.me/SLFranchiseBot"
+          target="_blank"
+          rel="noopener noreferrer"
+          aria-label="Открыть Telegram"
           size="xl"
           color="neutral"
           variant="ghost"
           icon="simple-icons:telegram"
         />
         <UButton
           to="https://vk.com/franshizasushi"
+          target="_blank"
+          rel="noopener noreferrer"
+          aria-label="Открыть VK"
           size="xl"
           color="neutral"
           variant="ghost"
           icon="simple-icons:vk"
         />
📝 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
<UColorModeButton size="xl" />
<UButton
to="https://t.me/SLFranchiseBot"
size="xl"
color="neutral"
variant="ghost"
icon="simple-icons:telegram"
/>
<UButton
to="https://vk.com/franshizasushi"
size="xl"
color="neutral"
variant="ghost"
icon="simple-icons:vk"
/>
</div>
</div>
<UColorModeButton size="xl" />
<UButton
to="https://t.me/SLFranchiseBot"
target="_blank"
rel="noopener noreferrer"
aria-label="Открыть Telegram"
size="xl"
color="neutral"
variant="ghost"
icon="simple-icons:telegram"
/>
<UButton
to="https://vk.com/franshizasushi"
target="_blank"
rel="noopener noreferrer"
aria-label="Открыть VK"
size="xl"
color="neutral"
variant="ghost"
icon="simple-icons:vk"
/>
</div>
</div>
🤖 Prompt for AI Agents
In apps/webinar/app/components/Header.vue around lines 7 to 23, the icon-only
UButton components lack accessible labels and do not open external links safely;
add aria-label attributes (e.g., aria-label="Telegram" and aria-label="VK") to
each icon-only UButton so screen readers can identify them, and ensure external
links open in a new tab with safe attributes by adding target="_blank" and
rel="noopener noreferrer" (or the equivalent UButton props) to those buttons.

Comment on lines 3 to 6
src="/sushi-main-logo.png"
alt=""
class="mx-auto h-20 w-auto motion-preset-pop"
class="h-12 w-fit motion-preset-pop"
>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add meaningful alt text for accessibility.

The logo is content, not purely decorative; empty alt hides it from SR.

   <img
     src="/sushi-main-logo.png"
-    alt=""
+    alt="Sushi Love"
     class="h-12 w-fit motion-preset-pop"
   >
📝 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
src="/sushi-main-logo.png"
alt=""
class="mx-auto h-20 w-auto motion-preset-pop"
class="h-12 w-fit motion-preset-pop"
>
<img
src="/sushi-main-logo.png"
alt="Sushi Love"
class="h-12 w-fit motion-preset-pop"
>
🤖 Prompt for AI Agents
In apps/webinar/app/components/HeaderLogo.vue around lines 3 to 6, the img tag
uses an empty alt attribute which hides meaningful logo content from screen
readers; replace the empty alt with a concise, descriptive string (for example
the application or company name like "Sushi Webinar logo" or whatever the brand
is) to convey the logo's purpose; keep it short, avoid redundant phrases like
"image of", and update any tests or snapshots if they assert on alt text.

Comment on lines +20 to +27
label: 'Записаться',
to: 'https://t.me/SLFranchiseBot',
target: '_blank',
trailingIcon: 'i-lucide-arrow-right',
ui: {
base: 'px-6 text-xl',
},
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add rel="noopener noreferrer" to the external link.

With target="_blank", omitting rel="noopener noreferrer" leaves the opener window exposed to reverse-tabnabbing. Please include the rel attribute to close that hole.

Apply this diff:

     target: '_blank',
+    rel: 'noopener noreferrer',
📝 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
label: 'Записаться',
to: 'https://t.me/SLFranchiseBot',
target: '_blank',
trailingIcon: 'i-lucide-arrow-right',
ui: {
base: 'px-6 text-xl',
},
},
label: 'Записаться',
to: 'https://t.me/SLFranchiseBot',
target: '_blank',
rel: 'noopener noreferrer',
trailingIcon: 'i-lucide-arrow-right',
ui: {
base: 'px-6 text-xl',
},
},
🤖 Prompt for AI Agents
In apps/webinar/app/components/Hero.vue around lines 20 to 27, the button/link
object sets target: '_blank' but lacks rel attributes; add rel: 'noopener
noreferrer' to that object (e.g., alongside to and target) so external links
opened in a new tab do not expose the opener (prevent reverse-tabnabbing).

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant