Commit Graph

88 Commits

Author SHA1 Message Date
isra el
06295ae7f1 refactor: read the shared data hooks instead of refetching by hand
Nine components each hand-rolled a useQuery against endpoints the typed
hooks in lib/api already cover: the current user in seven places, the
subscription in four, the device list in two, and stats in one. Each
copy repeated the endpoint, the envelope unwrapping and the cache key,
which is where both of the invalidation bugs fixed in c1942a4 came from.

They now call useCurrentUser, useSubscription, useDevices and
useGatewayStats. The polling behaviour in use-onboarding is preserved by
passing refetchInterval through the hooks' options, with the difference
that its polling now refreshes the same cache entries the rest of the
dashboard reads rather than maintaining a parallel copy of them.

update-app-modal and update-app-notification-bar previously destructured
the raw { data } envelope; useDevices unwraps it, so those consumers
read the array directly now.

Added createdAt and onboarding to the User type. Both are real fields
that components were already reading, and adding them lets the shared
hook feed the onboarding checklist, which had been the reason for a
separate query in the first place.

Verified: typecheck clean, build clean, 0 lint errors (21 warnings,
unchanged), 155 unit tests, 78 e2e.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 04:31:07 +03:00
isra el
3e4597294c refactor: turn on strict TypeScript and fix the 63 errors it found
My earlier estimate of 34 errors was wrong, and the reason is worth
recording: tsconfig carried "strictNullChecks": false as a duplicate key
AFTER the strict flag, and an explicit option beats the strict umbrella
even when --strict is passed on the CLI. So the measurement that produced
34 had silently excluded every null-safety error. The real number was 63.
Both duplicate keys are gone; strict: true now stands alone.

Most were implicit-any, but strict caught several genuine type lies:

- SendSmsPayload.deviceId and WebhookData._id were optional while both
  are interpolated into request paths, so an absent value would have hit
  /gateway/devices/undefined/send-sms or PATCH /webhooks/undefined.
- webhook-table's deviceName was typed string while buildDeviceLabel
  returns string | string[] and the Device cell already renders the
  array case. The type never described what the code produced.
- webhooks-section read `webhooks?.data?.length > 0`, comparing
  undefined against 0 while the query was still in flight.
- app-header declared a non-null Session while its own body guarded with
  session?.user throughout. Making the type honest surfaced four
  genuinely unguarded accesses.
- api-keys kept a local ApiKeyRow duplicating the shared ApiKey type, so
  the list callback annotated rows as one type while the hook returned
  the other. ApiKeyRow is now an alias and the two extra fields moved
  onto ApiKey.
- The notifications envelope typed its rows as unknown[], so the
  deliveries table's row type went entirely unchecked. Now a real
  WebhookNotification type.

The react-hook-form cluster (20 of the 63) was one root cause: zod's
.default() makes the input and output types differ, so z.infer (the
output) is not what the resolver takes. Fixed by typing the forms with
z.input and z.output separately, which changes nothing at runtime.

Also bumped target es5 to ES2017, which fixes the Set-iteration error
that made tsc --noEmit fail before any of this. Next compiles browser
output via SWC and its own browserslist, so bundle targeting is
unaffected. Added @types/papaparse and @types/react-syntax-highlighter,
and a typecheck script, since next build does not check test files.

No @ts-expect-error and no new any were used.

Verified: typecheck clean, build clean, 0 lint errors (21 warnings,
unchanged), 155 unit tests, 78 e2e.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 04:19:35 +03:00
isra el
469aa0025c fix: regrade the logo to the brand orange and crop its dead space
Copying the marketing logo did not fix the colour, because the marketing
artwork does not match --primary either. Measured, the mark held two
orange families: one already near brand at hsl(19,93%,52%) and an amber
cluster at hsl(28,98%,48%). The amber is what read as the old colour.

Regraded only the saturated orange fill onto hsl(21,90%,48%), preserving
shading, and left the tan body and dark figure alone. The rendered mark
now samples at hsl(22,89%,48%) against the text-primary "bee" beside it
at hsl(21,90%,48%).

The bigger problem was not colour. The source was a 500x500 canvas whose
artwork occupied a 387x319 box and only 24.7% of the pixels, drawn at
24px in the header. Roughly three quarters of that box rendered pure
white, so the mark was a faint smudge and whatever colour it carried was
barely visible. Cropped to the content on a square canvas.

The favicon carries the same regrade across all three of its frames.

Also fixes a latent flake in the RelativeTime component tests. They fed
the component dates derived from a hardcoded NOW while the component
reads the real clock, so "7 days ago" only held on 2026-07-18 and broke
the moment the date rolled over.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 03:07:42 +03:00
isra el
b2acba4c2c fix: tidy the community page and close mobile layout gaps
community-links was the only file in the app still wrapping a Button in
a Link, four times, producing an anchor around a button: invalid markup
and a nested interactive control that assistive tech announces twice.
Everywhere else already uses Button asChild. Two whole Card blocks sat
commented out, with an icon imported solely for that dead code.

community/page.tsx was the only dashboard section that never got the
mobile pass, keeping p-6 with no p-4 step and an unconditional text-3xl.
It was also the only section missing from the 375px overflow guard,
which is presumably how it was missed. Extracting the shared PageHeader,
which the messaging, webhooks and account layouts all repeated by hand,
fixes that outlier by construction.

Both billing limit grids and the promo modal were locked to two columns
at every width, so the meter captions had no room on a phone.

window.open kept a live opener handle back to the app in four places.
Browsers imply noopener for anchor targets but not for window.open.

The share dialog grid moves to 3 columns then 7. Worth stating plainly:
this is not an overflow fix. I expected 7 icons at grid-cols-4 to
overflow at 375px and the extended guard proved they do not. It was
only an awkward 4 + 3 split.

The overflow guard now covers 12 routes instead of 6 and opens a dialog,
since the densest layouts in the app only exist inside modals.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 02:20:39 +03:00
isra el
2b766e8019 fix: drop the phantom device status and share one error state
The Device type declared a `status` field that the schema does not have
and the API never sends, so `device.status === 'online'` was always
false. The badge took its colour from that phantom field and its text
from `enabled`, so an enabled, working device was styled exactly like a
disabled one. Both now come from `enabled`.

Devices, API keys and webhooks each rendered a bare unstyled
"Error: {error.message}" with no way to recover, putting raw transport
strings like "Request failed with status code 500" in front of users.
formatError already handles axios rejections and rate limits and was
used by none of them.

Adds components/shared/error-state.tsx as the counterpart to the
existing EmptyState, routing all three through formatError and offering
a retry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 02:10:33 +03:00
isra el
022a6bd2a6 fix: repair form labelling and navigation landmarks
None of the three password fields declared autoComplete, so password
managers could not tell them apart and would fill the saved password
into the wrong box. They now declare current-password and new-password.

The delete-account dialog, the most destructive action in the app, had
a label whose htmlFor pointed at an id the textarea never had, and an
email confirmation field with no label at all. Both are now wired, and
the controls moved out of DialogDescription, which Radix targets with
aria-describedby: opening the dialog previously read the labels and
placeholders back as one long description string.

Validation errors are now tied to their inputs via aria-describedby and
announced with role=alert instead of appearing silently.

confirmPassword required 4 characters while newPassword required 8, so
a five-character confirmation reported "Passwords must match" rather
than the actual length problem.

Five icon-only buttons had no accessible name: copy API key, copy
device ID, the API key menu, and both promo code copy buttons.

Four of the five nav landmarks were unlabelled, leaving them
indistinguishable in a screen reader's landmark list. Added a
skip-to-content link, since keyboard users otherwise tab the whole
sidebar on every page.

The delete-account label guard was confirmed to fail against the
previous markup.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 02:00:11 +03:00
isra el
628e5b09d1 feat: rework the SMS detail modal around chips and per-field copy
The bottom of the modal was a label/value table, and most of it repeated the
header: Direction restated the To/From already shown with its arrow icon, and
Number restated the number in the title. What remained was two facts wrapped
in table furniture.

Those facts are now inline chips: status, device, and the gateway ID in a
mono chip. The redundant rows are gone.

Copy moved from a single footer button to one button per field, next to the
thing it copies: the number in the header, the message body, and the gateway
ID. A lone "Copy text" button left the target to be inferred, and it could
only ever copy one of the three.

Reused the existing shared CopyButton rather than adding another, which also
brought its copied-state feedback and toast. It had no accessible name
though, so every icon-only copy button was announced identically. It now
names itself after what it copies, which is what lets the new test address
them individually.

Also overrode the dialog primitive's mobile text centring in this header: it
left the title on the left and the timestamp centred beneath it.

E2e reads the clipboard after each button and asserts all three return their
own value.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 00:17:16 +03:00
isra el
1999593e07 fix: day header overlap, mobile footer alignment, modal animation
Day headers in message history could cover message rows. Two distinct causes,
both reproduced before fixing:
- On mobile the header was pinned to top-14, the same sticky band the mobile
  search bar already occupies, so it detached and landed on top of rows.
  Being bg-muted/70 with a backdrop blur, row text bled through it, which is
  the distortion that was reported.
- On desktop it was pinned to top-0, behind the app header, and it covered
  rows scrolled beneath it and swallowed their clicks. Playwright surfaced
  this second one: a row click failed with "h3 intercepts pointer events".

Day headers are no longer sticky. A page holds 20 messages, so groups are
short and pinning bought little in exchange for those failures.

Footer links are left-aligned on mobile. Centred links in a single column had
no common edge to scan down.

Modal animation is now a quick fade. Dialogs slid in from the left and top
while zooming, which is the movement that read badly; the slide and zoom are
gone and the duration drops from 200ms to 150ms, applied to both the dialog
and alert-dialog primitives so every modal matches. Sheets still slide, since
that is what a drawer should do.

Guards: the overlap check runs at both mobile and desktop widths and also
asserts a row is still clickable after scrolling. Verified it fails against
the old markup rather than assuming a green test means coverage.

Fixture dates now anchor to local midnight instead of "N hours ago". A
2-hour-old message falls on the previous day when the suite runs shortly
after midnight, which made the Today/Yesterday assertions depend on the wall
clock. It failed exactly that way during this session.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 00:07:29 +03:00
isra el
ffbbabdbd3 fix: stack footer links vertically on mobile
Wrapped inline links produced a ragged two-line block with small tap targets.
Links now stack in a single column below sm and return to one row above it.

E2e asserts both directions: every link has a distinct y at 375px, and they
share a row at 1280px.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 21:27:21 +03:00
isra el
9cb1213efc feat: show relative timestamps with the exact time on hover
Device and API key cards rendered full timestamps ("Registered at: May 18,
2023, 1:42 PM"), which wrapped and dominated the cards at 375px.

New shared RelativeTime renders "7 days ago" with the exact date and time in a
tooltip. Formatting is delegated to date-fns, already a dependency and already
used by the webhook table, so no new package and no hand-rolled date parsing.
The only local rule is a "Just now" threshold under one minute.

Details:
- formatDistanceToNowStrict, not formatDistanceToNow, so it reads "3 minutes
  ago" rather than "about 3 minutes ago".
- Renders a real <time dateTime> element, keeping the machine-readable value
  in the DOM.
- The trigger is focusable, so the exact time is reachable by keyboard and not
  hover-only.
- Invalid or missing values render a caller-supplied fallback, so a key that
  has never been used still reads "Last used never" instead of "Invalid Date".

Labels shortened to match: "Registered at:" to "Registered", "Created at:" to
"Created".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 21:27:21 +03:00
isra el
0ea85c05e0 feat: clean up app chrome, rebuild footer and fix sidebar overlap
Top bar (app-header):
- Removed the Contribute button. It stays reachable from the footer.
- Removed the theme selector; it moves to the sidebar footer.
- Removed the mobile sheet for signed-in users: it duplicated the bottom tab
  bar for navigation and the avatar menu for identity. Signed-out visitors
  keep it, since auth pages have no tab bar.
The header is now brand on the left, account on the right.

Footer:
- Rebuilt as a slim single bar. A logged-in user is already converted, so the
  app does not need the marketing site's multi-column link farm. It borrows
  that footer's visual language (muted surface, muted-to-foreground hovers,
  green status pill) so the two still read as one product.
- Fixed the "cut by the side nav" bug. The footer was rendered in
  (app)/layout.tsx as a sibling of <main>, so it spanned the full viewport
  while the dashboard's fixed sidebar painted over its left 240px, and the
  fixed mobile tab bar covered its bottom edge. It is now rendered per
  section, inside each content column, with the dashboard copy padded clear
  of the tab bar.

Theme control:
- Moved to the sidebar footer and restyled from a dropdown into a three-way
  Light/Dark/System segmented control, which now has room to show all three
  states at once.

Sidebar icon:
- Dashboard uses LayoutDashboard instead of Home, matching the icon the
  avatar menu already uses for the same destination.

E2e harness, two real fixes found while verifying:
- The suite ran against `next dev`, which compiles routes on demand, so
  parallel workers hitting cold routes timed out at random. Switched to a
  production build; the suite went from 1.3m to ~31s.
- The survey modal (a Math.random() coin flip) and the update-app prompt
  (whenever a mocked device reports an old version) could open over any page.
  A Radix dialog marks the rest of the page aria-hidden while open, so every
  getByRole query found nothing and a different test failed each run. Both are
  now suppressed deterministically in the session helper.
Full suite: 16/16 on four consecutive runs.

New chrome.spec.ts guards the footer geometry against both the sidebar and
the mobile tab bar, asserts the top bar stays reduced, and asserts the
sidebar theme control actually toggles the dark class.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 20:21:54 +03:00
isra el
50d0902535 refactor: make transitions and entrance animations subtle
The entrance motion added in the previous rounds was too loud. Softened at
the token level and removed at the call sites where it replayed repeatedly.

Tokens (styles/main.css):
- fade-in 0.3s -> 0.15s
- fade-in-up 0.4s / 8px rise -> 0.2s / 3px rise
- check-pop 0.35s / 0.6-1.15-1 overshoot -> 0.2s / 0.92-1.02-1

Call sites, which mattered more than the token tuning:
- message-card: dropped animate-fade-in. Message history refetches on filter
  change, pagination and auto-refresh, so every row replayed its fade on
  every tick.
- get-started: dropped the per-index animationDelay stagger. The card polls
  every 10s, so any remount replayed a cascading six-row stagger.
- empty-state: dropped animate-fade-in from a static block.
- community-links: hover:scale-105 -> border and shadow change.
- onboarding progress bars: duration-500 -> duration-300.

Radix enter/exit animations on dialogs, dropdowns, selects, toasts and the
accordion are unchanged: they are short, conventional and expected. The
prefers-reduced-motion guard is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 19:52:29 +03:00
isra el
4ce6d00456 fix: resolve hydration, script-tag and invalid HTML nesting console errors
Three distinct issues surfaced by React 19 / Next 16:

1. next-themes script warning + hydration mismatch. ThemeProvider lived in
   the nested (app) layout, so its pre-paint <script> was re-rendered during
   client navigation ("Scripts inside React components are never executed
   when rendering on the client") and shifted the SSR/client tree. Moved it
   to the root layout via app/theme-provider.tsx, its canonical placement.

2. Invalid <p> nesting. Radix's DialogDescription and shadcn's
   CardDescription render a <p>, but call sites pass block content, giving
   "<p> cannot be a descendant of <p>" / "<div> cannot be a descendant of
   <p>". Fixed at the primitives: both now render a <div> (DialogDescription
   uses asChild so aria-describedby wiring is preserved), which fixes every
   call site at once. Also dropped a redundant wrapper in generate-api-key.

3. Nested <main>: the root layout wrapped children in <main> while the (app)
   layout renders its own. Removed the outer one.

Verified by driving the app with Playwright and capturing console output
across navigation and with the API key dialog open: zero hydration, script
tag, or nesting messages (previously several). Dark mode still applies.
Build, lint (0 errors), 28 unit tests, and 16 e2e green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 19:09:43 +03:00
isra el
6c3636104e feat: messaging subroutes (send/bulk/history/api) with full-width history
- new shared RouteTabs primitive (components/shared/route-tabs.tsx): a
  link-based segmented control; tabs are real routes so the active tab
  survives refresh and deep links are shareable; aria-current on the active
  link and the active pill scrolls into view on mobile
- messaging/layout.tsx renders the section header + tabs; views become
  subroutes: / (Send, focused composer), /bulk, /history (now full width,
  previously cramped in a half column), /api-guide (the 419-line guide gets
  its own URL instead of half the page)
- delete the obsolete client-side tab wrapper (components)/messaging.tsx and
  dead main-dashboard.tsx
- messaging e2e rewritten for route tabs, including a direct-load test of
  /dashboard/messaging/history as the refresh-survival guarantee

Build, 25 unit tests, and 12 e2e green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 07:32:50 +03:00
isra el
79e6dce8f7 fix: add rel attributes to external status link in footer
Adds rel='nofollow noopener noreferrer' to the target=_blank Status link.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 07:20:41 +03:00
isra el
eba534d208 feat: shared empty states, button press micro-interaction, gradient rename
- promote the message-history EmptyState into components/shared/empty-state
  and use it for the bare "No devices found" / "No API keys found"
  placeholders (icon + title + actionable hint, fade-in)
- button primitive: subtle active:scale press state (transition covers
  transform; the global prefers-reduced-motion guard still disables it)
- finish the deferred bg-gradient-to-* -> bg-linear-to-* rename (Tailwind v4
  canonical utility) across the remaining four alert/modal components

Build, 25 unit tests, and 6 e2e green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 06:48:02 +03:00
isra el
2623118df3 refactor: decompose 578-line webhooks-history into focused modules
Split app/(app)/dashboard/(components)/webhooks-history.tsx into a
webhooks-history/ folder (import path unchanged via index.tsx):

- use-filters.ts: filter state hook (device, webhook, event, status, preset
  and custom date ranges) with page reset handled in one place
- filters.tsx: the five filter selects + custom-range popover, deduplicated
  via a FilterField wrapper and option tables
- index.tsx: slim container on new useWebhooks / useWebhookNotifications
  hooks in lib/api
- promote the numbered pagination (previously duplicated verbatim between
  message-history and webhooks-history) to
  components/shared/numbered-pagination.tsx
- drop dead code: empty useEffect, never-set isLoading state, ~10 unused
  imports; replace the off-palette sky gradient with card tokens
- add a webhooks history e2e guard

Build, 25 unit tests, and 6 e2e green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 06:45:22 +03:00
isra el
6fe0a5dde0 refactor: sweep dashboard, auth and header off hardcoded gray/white onto tokens
Replace the common hardcoded gray/white color pairs (bg-white dark:bg-gray-800,
text-gray-900 dark:text-white, text-gray-500 dark:text-gray-400,
border-gray-200 dark:border-gray-700, etc.) with semantic tokens (bg-card,
text-foreground, text-muted-foreground, border-border, bg-muted) in the app
header, message history, verify-email and download pages, so light/dark stay
consistent and the theme is the single source of truth.

Build, 19 unit tests, and e2e green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 01:58:35 +03:00
isra el
c92740c401 test: add vitest + rtl + msw + playwright harness with mocked api
Establish the testing safety net for the web dashboard before refactoring.

- Vitest + React Testing Library + jsdom for unit/component tests
- MSW node server mocks the API; unhandled requests throw so tests can
  never reach a real backend
- Playwright e2e boots the real Next app but intercepts every /api/v1 call
  with shared fixtures, and mints a NextAuth session cookie to run authed
- Shared fixtures in test/fixtures.ts back both layers
- Seed tests: Button unit tests, MSW sanity checks, and a dashboard e2e
  happy path plus an auth-guard redirect

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 01:01:05 +03:00
isra el
3cd1d94b20 feat(web): add supporthq-customer support widget 2026-03-04 13:01:24 +03:00
isra el
4ef65b08ff chore(web): improve error message ui 2026-01-20 20:32:24 +03:00
Israel Abebe
0ffa945668 Merge pull request #152 from vernu/dev
Dev
2025-10-13 06:59:34 +03:00
isra el
7ce4efff70 refactor(web): cleanup and refactor dashboard layout components 2025-10-13 06:29:49 +03:00
isra el
d704b04e3d chore(web): show survey modal in dashboard 2025-09-30 19:56:40 +03:00
Yetmgeta Ewunetu
0d5f9a7cec webhook notifications for sent sms events (#145)
* webhook notifications for sms sent, sms delivered, sms failed, and unknown state, events

* updated deliverNotification function to deliver notifications for all events

* removed sms-status-updated filter

* updated payloads based on event type

* updated payloads based on event type

---------

Co-authored-by: Israel Abebe <vernu1997@gmail.com>
2025-09-29 19:43:24 +03:00
Yetmgeta Ewunetu
88e0b3df63 webhook notification history
* created the webhook notification history page and backend end point

* updated implementation and resolved comments

* updated sms-modal

* updated sms-modal
2025-09-18 23:07:53 +03:00
isra el
d1b311a7c5 chore(web): remove unused customer-support component 2025-08-07 20:33:42 +03:00
isra el
db3ac97af8 ui(web): rebuild web dashboard ui/ux 2025-06-04 20:37:23 +03:00
isra el
791babe430 chore: improve customer support and account deletion request flows 2025-04-04 22:59:15 +03:00
isra el
cce946e83a chore(web): add status page link in the footer 2025-03-15 18:57:09 +03:00
isra el
f0cbf92489 chore(web): improve landing page ui 2025-03-14 19:00:15 +03:00
isra el
28aee4d978 chore(web): update ToS and refund-policy 2025-03-14 18:59:34 +03:00
isra el
8dec46e01d chore(web): fix react hook dependencies 2025-03-09 13:08:26 +03:00
isra el
ca397a4554 chore(web): improve contribute modal 2025-03-09 11:25:17 +03:00
isra el
e262e23e2b chore(web): fix contribute modal 2025-02-24 00:20:45 +03:00
isra el
a199304e30 chore(web): improve contribute modal 2025-02-20 19:48:48 +03:00
isra el
b41262f5d3 chore(web): update crypto addresses 2025-01-20 18:20:19 +03:00
isra el
31ba75a816 refactor(web): cleanup dashboard components 2025-01-06 20:36:38 +03:00
isra el
9409d162ce chore(web): add contribute page 2025-01-06 06:15:13 +03:00
isra el
379e8e70e4 chore(web): improve webhooks section mobile responsiveness 2024-12-22 12:08:27 +03:00
isra el
f04fc47a66 feat(web): build webhook ui 2024-12-22 01:02:12 +03:00
isra el
6b2fbb574a ui(web): dark mode support 2024-12-06 23:43:15 +03:00
isra el
405f737449 refactor(web): major ui refactor 2024-12-01 02:31:46 +03:00
isra el
f207cf2439 init migration to app router 2024-10-26 15:20:58 +03:00
isra el
110401a3b8 chore(web): fix typo 2024-09-09 19:47:44 +03:00
isra el
ac693b5163 chore(web): update navbar 2024-08-31 16:05:50 +03:00
isra el
6cf6492cb4 chore(web): include ids in analytics 2024-08-31 16:04:41 +03:00
isra el
c0a4f2673a chore(web): add live support widget 2024-06-17 10:59:13 +03:00
isra el
33324f5515 chore(web): fix navbar and footer mobile responsiveness 2024-06-03 07:44:38 +03:00
isra el
f81961c5b6 chore(web): add support and contact sections 2024-06-03 07:24:05 +03:00