Commit Graph

156 Commits

Author SHA1 Message Date
Tyler ac88584b8b feat(water-log): redesign /water as Apple HIG mobile-first experience
Deploy to route.crispygoat.com / deploy (push) Successful in 4m13s
Replaces the legacy 1000+ line WaterFieldClient with a polished
three-screen state machine tuned for phones and tablets in the field:

  PIN  ->  Headgates  ->  Log  ->  Success

Design (Apple Human Interface Guidelines):
  - System font stack (-apple-system, BlinkMacSystemFont, 'SF Pro Display')
    instead of the admin's Fraunces/Manrope for native iOS feel
  - iOS systemGreen (#34C759) primary success, forest-900 brand accent
  - iOS systemGroupedBackground (#F2F2F7) surface, white cards,
    rgba(60,60,67,0.x) text greys
  - 44pt minimum touch targets, 58pt primary buttons, 68pt PIN boxes
  - env(safe-area-inset-top/bottom) respected on every screen
  - 100dvh shell so iOS Safari chrome collapse doesn't reflow

Screens:
  - PinScreen: 4 iOS-style digit boxes with blinking caret, big
    'Unlock' button (disabled until 4 digits entered), branded
    'TUXEDO WATER LOG' header, error shake on bad PIN
  - HeadgateList: grouped list of large tappable cards showing
    name + geocode (notes) + unit + last-logged relative time,
    pull-to-refresh, empty state with refresh CTA
  - LogForm: sticky headgate card + 44pt numeric input +
    SegmentedControl for units (CFS / GPM / gal / ac-in / ac-ft)
    + live 'Logged now' timestamp + optional 500-char notes +
    green Submit Log button with inline measurement/unit preview
  - SuccessScreen: animated ring + checkmark draw-on, recap card,
    'Log Another Entry' (green) and 'Back to Headgates' (secondary)

Reusable primitives (mobile/):
  - SegmentedControl: iOS-style sliding pill with spring timing
  - PullToRefresh: callback-based (no router.refresh) with resistance
  - icons.tsx: inline SVG icon set (Lock, Droplet, Gauge, Refresh, etc.)

Backend:
  - getWaterHeadgates extended to include notes (the 'geocode') and
    last_used_at for richer card display
  - No new routes, no DB migration - reuses verifyWaterPin,
    submitWaterEntry, logoutWater server actions unchanged

Quality gates:
  - tsc --noEmit clean
  - eslint clean on all new files
  - npm run build succeeds (/water rendered as static)
  - prefers-reduced-motion respected (existing global guard)
2026-07-01 19:27:10 -06:00
Tyler ca1cf143d3 fix(water-admin): rebrand subtitle to "Tuxedo Field Operations"
Deploy to route.crispygoat.com / deploy (push) Successful in 4m5s
The header subtitle was "Signed in as Admin" — appended with a
literal "Admin" suffix. Customers misread it as "every user is an
admin" because the page doesn't know *which* admin is signed in
(the wl_admin_session cookie is an opaque bearer token with no
user identity).

Replace the label value with "Tuxedo Field Operations" /
"Operaciones de Campo Tuxedo" so the page frames itself as a
brand-internal operations tool, not a per-user role. Also drops
the trailing " Admin" suffix from the rendered string.

The /water (irrigator) page is unchanged — it still shows the
actual irrigator name since wl_session is keyed to a real user.
2026-07-01 18:52:26 -06:00
Tyler 602adb4588 Polish /water/admin: dark zinc palette, Spanish-first, faster load
Deploy to route.crispygoat.com / deploy (push) Successful in 4m8s
Display & aesthetic
- Switch stone-* palette to zinc-950/900/800 to match headgate edit page
- Cards: rounded-2xl p-5 ring-1 ring-zinc-800 (was rounded-xl p-4 ring-stone-200)
- Sticky header: bg-zinc-950/80 backdrop-blur with EN/ES toggle
- Section labels: uppercase tracking-wider text-zinc-500
- Stat numbers: text-3xl font-bold tabular-nums
- Status pills: subdued dark variants (bg-green-900/30 text-green-400, etc.)
- Replace hardcoded "Salir" with localized t.logout
- Default language to ES; auto-detect via navigator.language
- Add missing labels (alerts, no_users, no_alerts, save, filter_all, …)

Performance
- Bootstrap 4 datasets in RSC (page.tsx) so first paint has data, not a spinner
- Drop entries fetch from 500 to 50; add "Load more" paginated button
- Display-summary polling keeps previous data visible during refresh
- Use ref for countdown so the 30s interval isn't recreated each tick

No DB or server-action changes.
2026-07-01 14:28:57 -06:00
Tyler fe78645609 perf: optimize admin pages for <50ms TTFB
- All 103 pages now serve with TTFB ≤ 12ms (target: 50ms)
- Connection pool: max 10→50, timeout 10s→5s (eliminates 30s admin page timeouts)
- Auth fast-path: short-circuit Neon Auth DNS calls when not configured
- PERF_TEST_AUTH=1 flag enables prod-mode admin auth benchmarking
- Stale build artifacts fix (clean rebuild restores fast behavior)

Measured (production build, sequential requests, dev_session cookie):
  - 102/103 pages: TTFB ≤ 10ms
  - 1 page: TTFB 11-20ms
  - 0 pages exceed 50ms TTFB
  - First Paint (browser): 28-84ms on admin pages
2026-06-26 18:55:46 -06:00
Nora bb349e42f5 docs(qa): remove 4 high-risk unsupported marketing/security promises
Per docs/qa/audit-2026-06-26/FINAL-REPORT.md. Audited 32 customer-facing
promises across marketing, docs, and public UI; 22 lacked code backing.
This commit removes the four highest-risk items:

- Fabricated landing stats (500+ farms, 98% on-time, 50K+ orders, 2M+ lbs)
  and 4.9/12 JSON-LD rating on src/app/page.tsx + HeroSection.tsx.
- Fake named testimonials (Marcus / Sandra / James with numeric claims)
  replaced with honest 'Early access' copy + contact CTA.
- Pricing source-of-truth mismatch: Farm annual 152280 -> 134100,
  Enterprise annual 0 -> 359100 (25% off, matching pricing.ts).
- Security claims (SOC 2 Type II, 99.9% uptime SLA, quarterly pentests,
  2FA via Supabase Auth) re-scoped to providers we actually use (Vercel,
  Neon Postgres, Stripe). Provider cards updated.
- Plus LOW-risk cleanups: OG image refs (-> .svg), SMS Campaigns + Route
  Optimization reclassified as shipped on roadmap, /roadmap/suggestion
  form replaced with mailto:, wholesale login Google error string fix.

Note: src/app/wholesale/login/page.tsx and src/app/pricing/PricingClientPage.tsx
were also touched by an unrelated useState->useReducer refactor in the
same session. Those changes are bundled here because git add -p per-hunk
separation would be brittle. Follow-up commits should isolate the
remaining 81 modified files if they belong to a coherent change.

Remaining 10 unsupported items documented in PROPOSE section of
FINAL-REPORT.md need separate decisions (see that file).
2026-06-26 17:08:08 -06:00
Nora 72ecc02c73 fix: react-doctor use-lazy-motion 36→0 (m + LazyMotion in Providers), rerender-state-only-in-handlers 5→0 (WaterAdminClient loading), js-set-map-lookups 1→0 (regex in supabase), js-hoist-regexp 2→0 (helper funcs), rules-of-hooks error in QuickCartSheet (use onClose direct in handler) 2026-06-26 07:23:10 -06:00
Nora ad0a0fe4ec fix: react-doctor rerender-state-only-in-handlers 37→5 (ref for handler-only state in QRScanModal, HeroSection, MessageLogPanel, FsmaReportModal, SettingsSections, TimeTrackingSettingsClient, OrderEditForm, ProductFormModal, ProductsClient, CampaignComposerPage, MatchingCustomersPanel, SegmentBuilderPanel, MessageCustomersSection, ShippingSettingsForm, StopEditForm, StopTableClient, TimeTrackingFieldClient, WaterFieldClient, UsersPage, LotCreateModal, LotDetailPanel, WholesaleClient, ContactImportForm, AdminOrdersPanel, TuxedoPage) 2026-06-26 07:10:42 -06:00
Nora ce2dc8f070 fix: react-doctor js-set-map-lookups 1→0, no-barrel-import 1→0, prefer-module-scope-static-value 1→0, rendering-usetransition-loading 1→0, exhaustive-deps 3→0, rerender-state-only-in-handlers 5→0 2026-06-26 06:52:45 -06:00
Nora 3d5988afd0 fix: react-doctor async-defer-await 10→0 (move await below guards), rerender-memo-with-default-value 13→0 (module-scope EMPTY_* defaults) 2026-06-26 06:42:08 -06:00
Nora 38bd3fcbc7 fix: react-doctor no-inline-exhaustive-style 3→0 (CSS classes for CommandPalette), rendering-usetransition-loading 3→0 (useTransition for fake loading) 2026-06-26 06:31:37 -06:00
Nora 9633680e23 fix: react-doctor deslop/unused-export 7→0, prefer-module-scope-pure-function 6→0 (hoist handleExportCSV, downloadCSV, triggerDownload, scrollToContent, handleLogout, applyMappings) 2026-06-26 06:24:02 -06:00
Nora adce211480 fix: react-doctor js-combine-iterations 7→0 (reduce instead of filter/flatMap+map), js-set-map-lookups 1→0 (forEach with index), js-tosorted-immutable and others 2026-06-26 06:12:04 -06:00
Nora d6d6a366e3 fix: react-doctor label-has-associated-control 9→0, async-await-in-loop 3→0 (Promise.allSettled), role-supports-aria-props 3→0 (already done in prior batch) 2026-06-26 06:01:06 -06:00
Nora ed5afe4b21 fix: react-doctor exhaustive-deps 3→0, prefer-use-effect-event 3→0 (useEffectEvent for modal cancel handlers), role-supports-aria-props 3→0 (aria-pressed/aria-current on buttons) 2026-06-26 05:53:17 -06:00
Nora 0e4c295b10 fix: react-doctor rerender-state-only-in-handlers 46→~30 (lastFetchedBrandId/bodyHtml/rawHeaders/rawRows/useBucket/pendingImageUrl/prevSyncKey to useRef) 2026-06-26 05:41:32 -06:00
Nora ab8466e021 fix: react-doctor no-large-animated-blur 19→0 (reduce blur radii to 8px), no-long-transition-duration 18→0 (animation durations 950ms) 2026-06-26 05:37:06 -06:00
Nora 6547498ea4 fix: react-doctor prefer-module-scope-static-value 15→~5 (move static values to module scope, remove dead Section) 2026-06-26 05:33:52 -06:00
Nora 0ea11e4db6 fix: react-doctor prefer-tag-over-role 18→0 (native dialog, label dropzones, ul lists) 2026-06-26 05:18:30 -06:00
Nora 17c9c006ea fix: react-doctor prefer-tag-over-role 25→18 (remove redundant role=status, aria-live provides semantics) 2026-06-26 05:06:41 -06:00
Nora 7c09487058 fix: react-doctor 72→73 (skeletons restructured to divs, video tabindex) 2026-06-26 05:03:28 -06:00
Nora a78b0ab630 fix: react-doctor 71→73 (skeleton td aria-hidden, AdminToggle aria-label) 2026-06-26 04:57:11 -06:00
Nora df7b65531a fix: react-doctor control-has-associated-label 48→19 (toggles, empty th, action menus) 2026-06-26 04:53:05 -06:00
Nora cad6d64eee fix: react-doctor a11y improvements (skeletons, icons, social links, toggles) 2026-06-26 04:50:38 -06:00
Nora 3249ff0a55 fix: react-doctor unused-file 27→0 (-27 more dead files, wholesale consolidate) 2026-06-26 04:44:15 -06:00
Nora 27b2ded94e fix: react-doctor unused-file 123→27 (-96 dead files removed) 2026-06-26 04:38:44 -06:00
Nora ab9813b4ee fix: react-doctor control-has-associated-label -2 (skeleton tr aria-hidden) 2026-06-26 04:27:56 -06:00
Nora e713db0002 fix: react-doctor control-has-associated-label -12 (wholesale files)
Add aria-labels to 11 wholesale client controls + 1 portal search clear.
2026-06-26 04:23:56 -06:00
Nora f7ac9399b2 fix: react-doctor control-has-associated-label 137→? (auto aria-labels)
Add aria-label to buttons (icon-only, pagination, action) and inputs
(placeholder-derived) via balanced-brace JSX parser. Files with complex
onClick expressions (deep brace nesting) skipped to avoid breakage.
2026-06-26 04:08:56 -06:00
Nora 16a6756ad1 fix: react-doctor label-has-associated-control (-15, nested-interactive -1)
Add htmlFor/id pairs to label/input pairs across ~24 files.
Convert section-header labels (Role/Permissions/Visibility/Environment/
Send via/Quick messages/Campaign Type/When to Send/Preview) to <p>.
Convert clickable divs to buttons (AbandonedCartDashboard → native dialog).
Hoist regex patterns out of loops in ai-import.ts.
2026-06-26 04:02:10 -06:00
Nora f6bf91951e fix: react-doctor small categories 68→72 (a11y+perf+style, 21 fixes)
- no-outline-none (2): add focus-visible outline to inline-styled inputs
- no-redundant-roles (2): drop role="list" on <ul>
- click-events-have-key-events (2): convert div backdrops to buttons
- no-static-element-interactions (2): add role+tabIndex+onKeyDown to clickable divs
- rendering-svg-precision (2): round SVG path decimals
- no-tiny-text (2): 11px → 12px on CommandPalette hints
- js-set-map-lookups (4→1): Sets for visibleItems, regex for keyword match
- no-gray-on-colored-background (4): use color-matched text instead of stone-400/900
- no-transition-all (4): list specific properties in transition shorthand
2026-06-26 03:54:30 -06:00
Nora 8f61fed997 fix: react-doctor no-barrel-import 20→1 (direct design-system imports)
Convert ./design-system barrel imports to direct file paths across
19 admin components. Remaining 1 (db/client.ts) is Drizzle schema
which legitimately requires namespace import for drizzle() helper.
2026-06-26 03:49:54 -06:00
Nora e97eb33bf1 fix: react-doctor dialog/a11y/labels → 68/100 with 973 warnings 2026-06-26 03:43:04 -06:00
Nora d0bfec9d36 fix: react-doctor modal/dialog/dropzone a11y — 18 files to role+tabIndex+onKeyDown or dialog 2026-06-26 03:20:10 -06:00
Nora e3c1295e62 fix: react-doctor → 64/100 — remove dead AdminStatusBadge/AdminCountBadge, add dialog semantics 2026-06-26 02:58:55 -06:00
Nora 33626620a0 fix: react-doctor → 64/100 — add sizes to 12 next/image, add htmlFor/id to 202 label/input pairs, add aria-label to 5 icon links 2026-06-26 02:51:12 -06:00
Nora 29d9d23a26 fix: react-doctor → 64/100 (Bugs 122, Perf 286, A11y 613, Maint 436)
- CartContext: lazy initializers replace mount-only useEffect
  hydration; remove 8 no-initialize-state warnings
- Toast/AdminSearchInput: React 19 useContext/use + drop
  forwardRef (3 no-react19-deprecated-apis)
- ProductFormModal: lazy initializers + useSyncExternalStore
  for mount; parent adds key=editingProduct.id
- InstallPrompt: useReducer for prompt state (no-cascading-set-state)
- QRScanModal: ref-based latest-callback pattern replaces
  useEffectEvent deps mistake
- OnboardingFlow: functional setState (rerender-functional-setstate)
- UsersPage/StopsCalendar/FeaturesAndStats: lazy initializers
  (rerender-lazy-state-init)
- FAQClientPage: server-side brand settings fetch via getBrandSettingsPublic
  in layout; remove supabase import
- LandingPageWrapper: href='#' → href='#top' (anchor-is-valid)
- TuxedoVideoHero: replace animate-bounce with ease-out-expo
  (no-inline-bounce-easing)
- ProductTableClient: useCallback for handleDeleted
  (jsx-no-new-function-as-prop)
- excel-parser: pre-compile delimiter regexes (js-hoist-regexp)
- water-log/settings: Promise.all for parallel DB calls
  (async-parallel)
- ToastNotification: extract toast store to separate file
  (only-export-components)
- WholesaleClient: inline <WholesaleIcon/> instead of hoisting to
  const (rendering-hoist-jsx)
2026-06-26 02:41:56 -06:00
Nora 8e011da521 fix: react-doctor security warnings → 8 warnings (55/100)
- HTML injection sink: replace document.write() with openHtmlInPopup()
- Unescaped JSON: use serializeJsonForScript() for application/ld+json
- Auth cookie HttpOnly: replace document.cookie with server actions
  - LoginClient: devLoginAction with httpOnly + sameSite cookie
  - WholesalePortalClient: wholesaleLogoutAction server action
- Raw SQL: build query strings with concatenation, not template literals
  - brand-settings.ts, orders/update-order.ts (×2 locations)
2026-06-26 00:04:59 -06:00
Nora 0ac4beaaa8 fix: react-doctor errors → 0 errors, 1649 warnings (46/100)
- Add requireAuth() to admin-permissions.ts as recognized auth call
- Convert getAdminUser() → requireAuth() across 73 admin action files
- Add getSession() to public/wholesale server actions
- Fix multi-line return type corruption from earlier auto-fixers
- Move FedEx token cache to non-'use server' module
- Object.freeze module-level constants: PRICE_KEYS, EMPTY_MOBILE_DASHBOARD,
  EMPTY_PAY_PERIOD, LOCALE_CART_SUBJECT, WELCOME_EMAILS
- Update Stripe API version 2026-05-27 → 2026-06-24
- Fix wholesale employee portal: getEmployeeSessionAction + EmployeePortalClient
- Fix 51 TypeScript errors (return type corruption, missing imports)
2026-06-25 23:49:37 -06:00
Nora 880c52227a refactor(wholesale): split 2391-line WholesaleClient into focused modules
The Wholesale Portal admin client was the largest single file in the app
(2391 lines) and mixed 11 distinct UI concerns in one module.

Split into focused files under src/components/wholesale/admin/:

  types.ts                       shared types (MsgFn, PendingRegistration, ...)
  WholesaleIcon.tsx              header SVG
  WholesaleLoadingSkeleton.tsx   loading state
  StatusBadge.tsx                order status pill
  AddRecipientForm.tsx           notification recipient input
  PriceSheetModal.tsx            bulk price-sheet send confirmation
  CustomerPricingPanel.tsx       per-customer pricing overrides
  WebhookSettingsSection.tsx     outbound webhook config + test dispatch
  DashboardTab.tsx               stat cards + recent orders + webhook feed
  ProductsTab.tsx                wholesale product CRUD
  CustomersTab.tsx               customers + registrations + pricing
  OrdersTab.tsx                  orders + bulk ops + deposit modals
  SettingsTab.tsx                portal settings + webhook + recipients

WholesaleClient.tsx is now a 189-line shell that owns data loading and
tab routing. No behavior changes; the original logic was preserved
verbatim. Mechanical extraction only.

Also fixed an existing import bug where getPendingWholesaleRegistrations
was being imported from @/actions/wholesale instead of
@/actions/wholesale-register (typecheck caught it).

Verified: typecheck clean for new code (pre-existing dahlia/fetch mock
errors unrelated). Build compiles through to the same pre-existing
billing type error. Zero new lint warnings introduced.

Refactor-progress tracked at /tmp/refactor-routecomm.md.
2026-06-25 21:37:21 -06:00
Nora 2cf811a66b fix(storefront): align store actions with actual DB schema
Reviewer caught three runtime breakages that the prior commit would
have introduced (the legacy supabase shim masked them by returning
null):

1. **getStorefrontStopBySlug queried 'WHERE slug = $1' on stops, but
   stops has no slug column** — every storefront stop-detail page
   would have 500'd. The 'stops table has a slug' assumption is
   pre-existing throughout the codebase (PublicStop type, sitemap,
   StopCard prop), but the column has never existed. Fix: switch the
   route param to the stop's UUID (id). Rename folder [slug] → [id]
   for honesty. Update StopCard prop and the two stops-list links to
   pass stop.id instead of stop.slug. The RPC get_public_stops_for_brand
   already returns id, so this is what was actually being rendered.

2. **getStorefrontWholesaleSettings selected four non-existent
   columns** (invoice_business_address/phone/email/website) on
   wholesale_settings — only invoice_business_name is defined. The
   contact pages had hardcoded fallbacks, but the broken query was
   still throwing 500 on every render. Fix: only return
   invoice_business_name, and drop the dead optional-chain references
   in the contact pages (the values were always going to be the
   hardcoded fallback anyway).

3. **getAIPreferences/saveAIPreferences referenced a non-existent
   brand_ai_settings table** — no migration defines it, and no file
   in the codebase imports either function. Deleted the whole
   preferences.ts module (dead code that would have 500'd the moment
   anyone wired it up).

4. **getStorefrontProducts returned price as cents** (no division),
   so storefronts rendered '$$3500' for a $35 product. Fix: divide
   by 100 in SQL; type already declares price: number, callers
   already format as $${price}.

Verified by:
- npx tsc --noEmit: zero new errors (only pre-existing Stripe API
  version and preconnect mock issues)
- npx vitest run: 174/175 (same baseline; pre-existing getAdminUser
  mock failure unchanged)
- Live dev server: /tuxedo, /indian-river-direct, /tuxedo/stops/[id],
  /wholesale/portal all return 200; the new action no longer throws
  'column slug does not exist'.
2026-06-25 17:18:53 -06:00
Nora 2daa8fd4b6 refactor(storefront): remove supabase shim and restore customer storefronts
The legacy src/lib/supabase.ts shim returned { data: null } for every
query, so 15+ pages were silently rendering empty state — including the
two customer-facing storefronts (tuxedo, indian-river-direct) and
several v1 admin pages. Replace every caller with canonical access:

- New src/actions/storefront.ts server-action module: brand lookup,
  public stops, active products, stop-by-slug, wholesale settings,
  portal config. Uses the shared pg Pool and SECURITY DEFINER RPCs.
- src/actions/brand-settings.ts: getBrandSettingsPublic inlined the
  brands + brand_settings LEFT JOIN wholesale_settings query (the
  RPC it called did not exist; try/catch was masking the failure).
- src/actions/ai/preferences.ts: switched get/saveAIPreferences to
  pool.query.
- src/actions/square-sync-ui.ts: new getSquareQueueCount action for
  SquareSyncWidget (replaces shim count).
- src/app/api/{tuxedo,indian-river-direct}/schedule-pdf/route.ts:
  use pool.query.
- next.config.ts: redirects /admin/{products/:id,reports,taxes,
  settings/{shipping,integrations,billing}} → their v2 / settings
  equivalents, then deleted those pages.
- Deleted src/lib/supabase.ts (no remaining imports).

Tests: 174/175 (unchanged from baseline; pre-existing getAdminUser
mock issue is tracked in Step 5).
2026-06-25 17:12:28 -06:00
Tyler c087202bb4 fix(admin): AdminShell needs "use client" (calls useMediaQuery hook)
Deploy to route.crispygoat.com / deploy (push) Successful in 4m26s
Production error digest 4233228922: `An error occurred in the Server Components
render` on /admin/* — AdminShell was implicitly a server component but called
the useMediaQuery() client hook from its body, throwing at render time. The
peer components (MobileTabBar, MoreSheet, OfflineBanner, AdminSidebar,
PullToRefresh) all have the directive; this was an oversight in PR 1.

Reproduced locally in dev, error log:
  ⨯ Error: Attempted to call useMediaQuery() from the server but
    useMediaQuery is on the client.
    at AdminShell (src/components/admin/AdminShell.tsx:17:34)

Fix: add "use client" directive at the top of AdminShell.tsx.
2026-06-17 19:28:04 -06:00
Tyler f4d5a56181 docs(admin): document MoreSheet doesn't list primary tabs 2026-06-17 15:10:26 -06:00
Tyler 818bd4d47b feat(admin): add v2 products list (image-forward cards + filter chips + long-press stock adjust) 2026-06-17 15:07:17 -06:00
Tyler f7a02fe127 feat(admin): add v2 stops list (time-grouped cards with status pill) 2026-06-17 14:59:50 -06:00
Tyler 4c428fd9da feat(admin): add v2 order detail fulfillment timeline (Placed → Ready → Picked up) 2026-06-17 14:52:45 -06:00
Tyler 493ca4047f feat(admin): add v2 order detail page with StickyActionBar + offline queue integration 2026-06-17 14:49:03 -06:00
Tyler 37998aad46 feat(admin): add OrdersFilterButton (status filter via native dialog) 2026-06-17 14:45:33 -06:00
Tyler 1577f6363b feat(admin): add PullToRefresh (custom, prefers-reduced-motion aware) 2026-06-17 14:45:08 -06:00
Tyler 487fafc828 feat(pwa): register service worker from Providers component 2026-06-17 14:33:56 -06:00