@carrierllc/mcp 0.2.19 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +22 -1
  2. package/dist/chunk-ZII4CD4U.js +461 -0
  3. package/dist/chunk-ZII4CD4U.js.map +1 -0
  4. package/dist/cli.js +1294 -105
  5. package/dist/cli.js.map +1 -1
  6. package/dist/index.js +144 -315
  7. package/dist/index.js.map +1 -1
  8. package/package.json +1 -1
  9. package/plugin/carrier/commands/storefront.md +24 -12
  10. package/plugin/carrier/skills/carrier-operations/SKILL.md +4 -1
  11. package/templates/storefront/{next.config.ts → next.config.mjs} +2 -6
  12. package/templates/storefront/public/brand/logo.svg +13 -0
  13. package/templates/storefront/src/app/activate/[orderId]/ActivateClient.tsx +12 -12
  14. package/templates/storefront/src/app/checkout/[templateId]/CheckoutClient.tsx +5 -5
  15. package/templates/storefront/src/app/checkout/success/CheckoutSuccessClient.tsx +29 -29
  16. package/templates/storefront/src/app/checkout/success/page.tsx +1 -1
  17. package/templates/storefront/src/app/contact/page.tsx +33 -25
  18. package/templates/storefront/src/app/dashboard/page.tsx +7 -4
  19. package/templates/storefront/src/app/globals.css +72 -82
  20. package/templates/storefront/src/app/help/page.tsx +22 -17
  21. package/templates/storefront/src/app/layout.tsx +15 -4
  22. package/templates/storefront/src/app/page.tsx +16 -7
  23. package/templates/storefront/src/app/shop/ShopClient.tsx +7 -3
  24. package/templates/storefront/src/app/sign-in/[[...sign-in]]/page.tsx +1 -1
  25. package/templates/storefront/src/app/sign-up/[[...sign-up]]/StorefrontSignUpClient.tsx +16 -16
  26. package/templates/storefront/src/app/sign-up/[[...sign-up]]/page.tsx +1 -1
  27. package/templates/storefront/src/brand.config.ts +3 -3
  28. package/templates/storefront/src/components/faq/FaqSection.tsx +23 -26
  29. package/templates/storefront/src/components/footer/StorefrontFooter.tsx +37 -22
  30. package/templates/storefront/src/components/landing/FinalCTA.tsx +28 -0
  31. package/templates/storefront/src/components/landing/HeroSection.tsx +57 -24
  32. package/templates/storefront/src/components/landing/HowItWorks.tsx +44 -0
  33. package/templates/storefront/src/components/landing/PlanCard.tsx +58 -28
  34. package/templates/storefront/src/components/nav/StorefrontNav.tsx +59 -24
  35. package/templates/storefront/src/components/theme/BrandStyles.tsx +8 -0
  36. package/templates/storefront/src/components/theme/ThemeToggle.tsx +27 -0
  37. package/templates/storefront/src/components/theme/clerk-appearance.ts +33 -35
  38. package/templates/storefront/src/components/theme/theme-provider.tsx +25 -32
  39. package/templates/storefront/src/components/theme/theme-script.tsx +4 -7
  40. package/templates/storefront/src/lib/complete-email-sign-up.ts +3 -3
  41. package/templates/storefront/src/vendor/ui/brand.ts +6 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carrierllc/mcp",
3
- "version": "0.2.19",
3
+ "version": "0.3.0",
4
4
  "description": "Carrier MCP \u2014 natural-language control of MVNO/eSIM fleets via eSIMVault OCS. Stdio mode for direct integration with Claude Desktop, Cursor, Windsurf, and MCP-compatible clients. Ships the `carrier` CLI (plugin install + white-label eSIM storefront scaffold).",
5
5
  "license": "MIT",
6
6
  "author": "Carrier (Lifecycle Innovations Limited)",
@@ -1,21 +1,33 @@
1
1
  ---
2
- description: Scaffold, white-label, and deploy your eSIM storefront
3
- argument-hint: "[create|deploy|rebrand] [dir]"
2
+ description: Scaffold, white-label, logo, and deploy your eSIM storefront
3
+ argument-hint: "[create|logo|deploy|rebrand] [dir]"
4
4
  ---
5
5
 
6
6
  # Storefront
7
7
 
8
- Roll out the customer-facing eSIM storefront (Mango template, white-labeled) that sits on top of your Carrier fleet.
8
+ Roll out the customer-facing eSIM storefront (Mango template, white-labeled) on top of your Carrier fleet.
9
9
 
10
- This wraps the `@carrierllc/cli` scaffolder.
10
+ Works for **any developer** with Carrier MCP (`https://mcp.carrier.llc/mcp`) or the `carrier` CLI. You do not need an image-API key on your laptop.
11
11
 
12
- - **create** → run `npx @carrierllc/cli site create <dir>` (or `carrier site create`). Capture brand name, domain, accent color, support email; it generates `src/brand.config.ts` and a buildable Next.js storefront.
13
- - **rebrand** edit `src/brand.config.ts` in the storefront dir (name, colors, tagline, domain, support, social) — the single white-label surface — then rebuild.
14
- - **deploy** → `npx @carrierllc/cli site deploy <dir>` builds and pushes to Cloudflare Pages. Requires `wrangler login`.
12
+ - **create** → `carrier site create <dir>`
13
+ Captures brand name, domain, accent, support email. Writes `src/brand.config.ts` **and** `public/brand/logo.svg` (unique mark from the brand name + accent).
14
+ - **logo** → `carrier site logo <dir>` or MCP tool `generate_storefront_logo`
15
+ Regenerates the mark. SVG always. PNG is added only when Carrier (or this box) has an OpenAI image key — optional, never required.
16
+ - **rebrand** → edit `src/brand.config.ts`, then `carrier site logo <dir>` and rebuild.
17
+ - **deploy** → `carrier site deploy <dir>` (Cloudflare Workers). Requires `wrangler login`.
15
18
 
16
- Wire-up notes:
17
- - The storefront talks to your control plane via `NEXT_PUBLIC_CARRIER_API_URL` (`.env.local`).
18
- - Plans shown to customers come from `pricing_plans` / `list_package_templates`; keep them in sync.
19
- - Checkout provisions through the same fleet — confirm `credential_status` is healthy before going live.
19
+ MCP (any authenticated Carrier user):
20
20
 
21
- After deploy, verify the live URL renders and the plans match `pricing_plans`.
21
+ ```
22
+ generate_storefront_logo
23
+ name: Bananas
24
+ accent: #FF8A00
25
+ tagline: Travel eSIM that just works.
26
+ ```
27
+
28
+ Write the returned `svg` to `public/brand/logo.svg`. Nav and icons read that path.
29
+
30
+ Wire-up:
31
+ - `NEXT_PUBLIC_CARRIER_API_URL` in `.env.local`
32
+ - Plans from `list_package_templates` / `pricing_plans`
33
+ - Confirm `credential_status` before going live
@@ -30,6 +30,9 @@ Carrier is a programmable connectivity control plane (the "Stripe of telecom") e
30
30
  ## Fleet & platform
31
31
  `fleet_health` · `fleet_health_app` · `list_recent_ocs_events` · `rate_limit_status` · `carrier_ocs_calls_per_min` · `environment_info` · `credential_status` · `rotate_credentials` · `list_reseller_accounts` · `get_reseller_info` · `send_sms`
32
32
 
33
+ ## Storefront
34
+ `generate_storefront_logo` (any authenticated MCP user) · CLI `carrier site create` writes `public/brand/logo.svg` · `carrier site logo` regenerates. Optional PNG if Carrier/worker has `OPENAI_API_KEY`.
35
+
33
36
  ## Gated surfaces (not free-tier defaults)
34
37
  - **Steel ui_agent_*** — requires `STEEL_API_KEY` / ops config; UI-only OCS ops.
35
38
  - **`stripe_proxy_call`** — Pro/Enterprise Stripe Agent Toolkit multiplexer.
@@ -40,4 +43,4 @@ Carrier is a programmable connectivity control plane (the "Stripe of telecom") e
40
43
  2. **Confirm money & live-service changes** — status changes, balance/package mutations, wallet top-ups, `clean_all_packages`, `rotate_credentials`, outbound `send_sms`.
41
44
  3. **Right quantity only** — never over-provision; check `credit_balance` and (managed mode) `wallet_balance` first.
42
45
  4. **Diagnose, don't guess** — use `diagnose_subscriber` + event tools for faults.
43
- 5. The storefront layer (white-label Mango template) is scaffolded/deployed via `@carrierllc/cli`; keep its plans synced with `pricing_plans`.
46
+ 5. Storefront: `carrier site create` / `carrier site logo`, or MCP `generate_storefront_logo`. SVG mark is generated for every operator no local image-API key required. Keep plans synced with `pricing_plans`.
@@ -1,4 +1,3 @@
1
- import type { NextConfig } from "next";
2
1
  import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare";
3
2
  import { fileURLToPath } from "url";
4
3
  import { dirname } from "path";
@@ -8,15 +7,12 @@ initOpenNextCloudflareForDev();
8
7
  const __filename = fileURLToPath(import.meta.url);
9
8
  const __dirname = dirname(__filename);
10
9
 
11
- const nextConfig: NextConfig = {
10
+ /** @type {import("next").NextConfig} */
11
+ const nextConfig = {
12
12
  images: {
13
13
  unoptimized: true,
14
14
  },
15
15
  outputFileTracingRoot: __dirname,
16
- eslint: {
17
- // Template ships with a minimal ESLint config; lint separately via `pnpm lint`
18
- ignoreDuringBuilds: true,
19
- },
20
16
  };
21
17
 
22
18
  export default nextConfig;
@@ -0,0 +1,13 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512" role="img" aria-label="Carrier logo">
3
+ <defs>
4
+ <linearGradient id="lgcf797g" x1="0" y1="0" x2="1" y2="1">
5
+ <stop offset="0%" stop-color="#ff9c75"/>
6
+ <stop offset="55%" stop-color="#FF6B35"/>
7
+ <stop offset="100%" stop-color="#984625"/>
8
+ </linearGradient>
9
+ </defs>
10
+ <rect width="512" height="512" rx="96" fill="#FAFAF7"/>
11
+ <path d="M256 64 C320 64 400 96 424 176 C448 256 424 352 344 416 C280 464 232 464 168 416 C88 352 64 256 88 176 C112 96 192 64 256 64 Z" fill="url(#lgcf797g)"/>
12
+ <text x="256" y="300" text-anchor="middle" font-family="Georgia, 'Times New Roman', serif" font-size="220" font-weight="700" fill="#FFF8EB">C</text>
13
+ </svg>
@@ -51,34 +51,34 @@ export function ActivateClient({ orderId }: Props) {
51
51
  <div className="inline-flex items-center justify-center size-14 rounded-full bg-emerald-500/10 border border-emerald-500/30 mb-4">
52
52
  <span className="text-2xl">✓</span>
53
53
  </div>
54
- <h1 className="text-2xl font-bold text-white">Your eSIM is ready</h1>
55
- <p className="mt-2 text-sm text-white/50">Order: {orderId}</p>
54
+ <h1 className="text-2xl font-bold text-sand-900 dark:text-sand-50">Your eSIM is ready</h1>
55
+ <p className="mt-2 text-sm text-sand-500">Order: {orderId}</p>
56
56
  </div>
57
57
 
58
58
  {/* QR Code placeholder */}
59
- <div className="rounded-2xl border border-white/10 bg-white/5 p-8 text-center mb-8">
60
- <p className="text-white/60 text-sm mb-2">
59
+ <div className="rounded-2xl border border-sand-200 dark:border-sand-800 bg-white dark:bg-sand-900 p-8 text-center mb-8">
60
+ <p className="text-sand-600 dark:text-sand-400 text-sm mb-2">
61
61
  Your QR code will appear here once your order is confirmed.
62
62
  </p>
63
- <p className="text-white/30 text-xs">
63
+ <p className="text-sand-400 text-xs">
64
64
  This usually takes less than a minute. Refresh if needed.
65
65
  </p>
66
66
  </div>
67
67
 
68
68
  {/* Optional phone / WhatsApp collection */}
69
- <div className="rounded-2xl border border-white/10 bg-white/5 p-6">
69
+ <div className="rounded-2xl border border-sand-200 dark:border-sand-800 bg-white dark:bg-sand-900 p-6">
70
70
  <div className="flex items-start gap-3 mb-4">
71
71
  <div className="shrink-0 mt-0.5">
72
- <span className="inline-flex items-center justify-center size-5 rounded-full bg-white/10 text-[10px] text-white/50">
72
+ <span className="inline-flex items-center justify-center size-5 rounded-full bg-white/10 text-[10px] text-sand-500">
73
73
  ?
74
74
  </span>
75
75
  </div>
76
76
  <div>
77
- <p className="text-sm font-medium text-white">
77
+ <p className="text-sm font-medium text-sand-900 dark:text-sand-50">
78
78
  Get eSIM updates via WhatsApp{" "}
79
- <span className="text-white/30 font-normal">(optional)</span>
79
+ <span className="text-sand-400 font-normal">(optional)</span>
80
80
  </p>
81
- <p className="text-xs text-white/40 mt-0.5">
81
+ <p className="text-xs text-sand-500 mt-0.5">
82
82
  We&apos;ll send your QR code and activation status. You can skip this.
83
83
  </p>
84
84
  </div>
@@ -97,7 +97,7 @@ export function ActivateClient({ orderId }: Props) {
97
97
  value={phone}
98
98
  onChange={(e) => setPhone(e.target.value)}
99
99
  placeholder="+1 555 000 0000"
100
- className="flex-1 rounded-lg border border-white/10 bg-black/30 px-3 py-2 text-sm text-white placeholder-white/20 focus:border-[var(--brand-accent)] focus:outline-none"
100
+ className="flex-1 rounded-lg border border-sand-200 dark:border-sand-800 bg-sand-50 dark:bg-sand-950 px-3 py-2 text-sm text-sand-900 dark:text-sand-50 placeholder:text-sand-400 focus:border-[var(--brand-accent)] focus:outline-none"
101
101
  />
102
102
  <button
103
103
  type="submit"
@@ -113,7 +113,7 @@ export function ActivateClient({ orderId }: Props) {
113
113
  <button
114
114
  type="button"
115
115
  onClick={() => setSubmitted(true)}
116
- className="w-full text-center text-xs text-white/30 hover:text-white/50 py-1"
116
+ className="w-full text-center text-xs text-sand-400 hover:text-sand-500 py-1"
117
117
  >
118
118
  Skip for now
119
119
  </button>
@@ -86,11 +86,11 @@ export function CheckoutClient({ plan, session }: Props) {
86
86
  if (checkoutError) {
87
87
  return (
88
88
  <div className="mx-auto max-w-lg px-4 py-32 text-center">
89
- <p className="text-white/80">{checkoutError}</p>
90
- <p className="text-sm text-white/30 mt-2">{plan.name}</p>
89
+ <p className="text-sand-700 dark:text-sand-200">{checkoutError}</p>
90
+ <p className="text-sm text-sand-400 mt-2">{plan.name}</p>
91
91
  <a
92
92
  href="/shop"
93
- className="mt-6 inline-block rounded-full border border-white/20 px-6 py-2 text-sm text-white hover:bg-white/10 transition-colors"
93
+ className="mt-6 inline-block rounded-full border border-sand-300 dark:border-sand-700 px-6 py-2 text-sm text-sand-900 dark:text-sand-50 hover:bg-white/10 transition-colors"
94
94
  >
95
95
  ← Back to shop
96
96
  </a>
@@ -101,10 +101,10 @@ export function CheckoutClient({ plan, session }: Props) {
101
101
  return (
102
102
  <div className="mx-auto max-w-lg px-4 py-32 text-center">
103
103
  <div className="h-8 w-8 rounded-full border-2 border-[var(--brand-accent)] border-t-transparent animate-spin mx-auto mb-4" />
104
- <p className="text-white/60">
104
+ <p className="text-sand-600 dark:text-sand-400">
105
105
  {loading ? "Preparing your checkout…" : "Redirecting to payment…"}
106
106
  </p>
107
- <p className="text-sm text-white/30 mt-2">{plan.name}</p>
107
+ <p className="text-sm text-sand-400 mt-2">{plan.name}</p>
108
108
  </div>
109
109
  );
110
110
  }
@@ -11,7 +11,7 @@
11
11
  * The orderId is verified server-side before this component renders.
12
12
  */
13
13
  import { useState, FormEvent, useEffect, useCallback } from "react";
14
- import { useSignUp, useSignIn } from "@clerk/nextjs/legacy";
14
+ import { useSignUp, useSignIn } from "@clerk/nextjs";
15
15
  import { useClerk, useAuth } from "@clerk/nextjs";
16
16
  import { useRouter } from "next/navigation";
17
17
  import { completeEmailSignUpAfterVerify } from "@/lib/complete-email-sign-up";
@@ -166,7 +166,7 @@ export function CheckoutSuccessClient({
166
166
  <div className="flex min-h-[calc(100vh-4rem)] items-center justify-center px-4">
167
167
  <div className="text-center space-y-3">
168
168
  <div className="mx-auto size-8 animate-spin rounded-full border-2 border-[var(--brand-accent)] border-t-transparent" />
169
- <p className="text-sm text-white/60">
169
+ <p className="text-sm text-sand-600 dark:text-sand-400">
170
170
  {step === "redirecting" ? "Activating your eSIM…" : "Loading…"}
171
171
  </p>
172
172
  </div>
@@ -181,8 +181,8 @@ export function CheckoutSuccessClient({
181
181
  <div className="rounded-2xl border border-emerald-500/30 bg-emerald-500/10 px-6 py-4">
182
182
  <p className="text-lg font-semibold text-emerald-300">✓ Payment successful!</p>
183
183
  </div>
184
- <div className="rounded-2xl border border-white/10 bg-white/5 p-8 backdrop-blur space-y-4">
185
- <p className="text-sm text-white/80">We could not link this purchase to your account.</p>
184
+ <div className="rounded-2xl border border-sand-200 dark:border-sand-800 bg-white dark:bg-sand-900 p-8 backdrop-blur space-y-4">
185
+ <p className="text-sm text-sand-700 dark:text-sand-200">We could not link this purchase to your account.</p>
186
186
  {error && (
187
187
  <p className="rounded-lg border border-red-500/30 bg-red-500/10 px-3 py-2 text-xs text-red-300">
188
188
  {error}
@@ -213,17 +213,17 @@ export function CheckoutSuccessClient({
213
213
  </div>
214
214
 
215
215
  {/* Auth card */}
216
- <div className="rounded-2xl border border-white/10 bg-white/5 p-8 backdrop-blur">
216
+ <div className="rounded-2xl border border-sand-200 dark:border-sand-800 bg-white dark:bg-sand-900 p-8 backdrop-blur">
217
217
  {/* Mode toggle */}
218
218
  {step === "credentials" && (
219
- <div className="flex rounded-lg border border-white/10 p-1 mb-6">
219
+ <div className="flex rounded-lg border border-sand-200 dark:border-sand-800 p-1 mb-6">
220
220
  <button
221
221
  type="button"
222
222
  onClick={() => { setMode("signup"); setError(null); }}
223
223
  className={`flex-1 rounded-md py-1.5 text-sm font-medium transition-colors ${
224
224
  mode === "signup"
225
- ? "bg-[var(--brand-accent)] text-white"
226
- : "text-white/40 hover:text-white/70"
225
+ ? "bg-[var(--brand-accent)] text-sand-900 dark:text-sand-50"
226
+ : "text-sand-500 hover:text-sand-600 dark:text-sand-300"
227
227
  }`}
228
228
  >
229
229
  Create account
@@ -233,8 +233,8 @@ export function CheckoutSuccessClient({
233
233
  onClick={() => { setMode("signin"); setError(null); }}
234
234
  className={`flex-1 rounded-md py-1.5 text-sm font-medium transition-colors ${
235
235
  mode === "signin"
236
- ? "bg-[var(--brand-accent)] text-white"
237
- : "text-white/40 hover:text-white/70"
236
+ ? "bg-[var(--brand-accent)] text-sand-900 dark:text-sand-50"
237
+ : "text-sand-500 hover:text-sand-600 dark:text-sand-300"
238
238
  }`}
239
239
  >
240
240
  Sign in
@@ -245,15 +245,15 @@ export function CheckoutSuccessClient({
245
245
  {/* Sign-up: credentials step */}
246
246
  {step === "credentials" && mode === "signup" && (
247
247
  <>
248
- <h2 className="text-base font-semibold text-white mb-1">Create your account</h2>
249
- <p className="text-xs text-white/40 mb-5">
248
+ <h2 className="text-base font-semibold text-sand-900 dark:text-sand-50 mb-1">Create your account</h2>
249
+ <p className="text-xs text-sand-500 mb-5">
250
250
  {emailLocked
251
251
  ? "Use the email from your checkout to create your account."
252
252
  : "Email and password — no phone required."}
253
253
  </p>
254
254
  <form onSubmit={handleSignUp} className="space-y-4">
255
255
  <div>
256
- <label className="block text-xs font-medium text-white/60 mb-1" htmlFor="su-email">
256
+ <label className="block text-xs font-medium text-sand-600 dark:text-sand-400 mb-1" htmlFor="su-email">
257
257
  Email address
258
258
  </label>
259
259
  <input
@@ -264,12 +264,12 @@ export function CheckoutSuccessClient({
264
264
  readOnly={emailLocked}
265
265
  value={email}
266
266
  onChange={(e) => setEmail(e.target.value)}
267
- className="w-full rounded-lg border border-white/10 bg-black/30 px-3 py-2 text-sm text-white placeholder-white/20 focus:border-[var(--brand-accent)] focus:outline-none"
267
+ className="w-full rounded-lg border border-sand-200 dark:border-sand-800 bg-sand-50 dark:bg-sand-950 px-3 py-2 text-sm text-sand-900 dark:text-sand-50 placeholder:text-sand-400 focus:border-[var(--brand-accent)] focus:outline-none"
268
268
  placeholder="you@example.com"
269
269
  />
270
270
  </div>
271
271
  <div>
272
- <label className="block text-xs font-medium text-white/60 mb-1" htmlFor="su-password">
272
+ <label className="block text-xs font-medium text-sand-600 dark:text-sand-400 mb-1" htmlFor="su-password">
273
273
  Password
274
274
  </label>
275
275
  <input
@@ -280,7 +280,7 @@ export function CheckoutSuccessClient({
280
280
  minLength={8}
281
281
  value={password}
282
282
  onChange={(e) => setPassword(e.target.value)}
283
- className="w-full rounded-lg border border-white/10 bg-black/30 px-3 py-2 text-sm text-white placeholder-white/20 focus:border-[var(--brand-accent)] focus:outline-none"
283
+ className="w-full rounded-lg border border-sand-200 dark:border-sand-800 bg-sand-50 dark:bg-sand-950 px-3 py-2 text-sm text-sand-900 dark:text-sand-50 placeholder:text-sand-400 focus:border-[var(--brand-accent)] focus:outline-none"
284
284
  placeholder="Min. 8 characters"
285
285
  />
286
286
  </div>
@@ -303,15 +303,15 @@ export function CheckoutSuccessClient({
303
303
  {/* Sign-in: credentials step */}
304
304
  {step === "credentials" && mode === "signin" && (
305
305
  <>
306
- <h2 className="text-base font-semibold text-white mb-1">Sign in</h2>
307
- <p className="text-xs text-white/40 mb-5">
306
+ <h2 className="text-base font-semibold text-sand-900 dark:text-sand-50 mb-1">Sign in</h2>
307
+ <p className="text-xs text-sand-500 mb-5">
308
308
  {emailLocked
309
309
  ? "Sign in with the email from your checkout to activate your eSIM."
310
310
  : "Sign in to activate your eSIM."}
311
311
  </p>
312
312
  <form onSubmit={handleSignIn} className="space-y-4">
313
313
  <div>
314
- <label className="block text-xs font-medium text-white/60 mb-1" htmlFor="si-email">
314
+ <label className="block text-xs font-medium text-sand-600 dark:text-sand-400 mb-1" htmlFor="si-email">
315
315
  Email address
316
316
  </label>
317
317
  <input
@@ -322,12 +322,12 @@ export function CheckoutSuccessClient({
322
322
  readOnly={emailLocked}
323
323
  value={email}
324
324
  onChange={(e) => setEmail(e.target.value)}
325
- className="w-full rounded-lg border border-white/10 bg-black/30 px-3 py-2 text-sm text-white placeholder-white/20 focus:border-[var(--brand-accent)] focus:outline-none"
325
+ className="w-full rounded-lg border border-sand-200 dark:border-sand-800 bg-sand-50 dark:bg-sand-950 px-3 py-2 text-sm text-sand-900 dark:text-sand-50 placeholder:text-sand-400 focus:border-[var(--brand-accent)] focus:outline-none"
326
326
  placeholder="you@example.com"
327
327
  />
328
328
  </div>
329
329
  <div>
330
- <label className="block text-xs font-medium text-white/60 mb-1" htmlFor="si-password">
330
+ <label className="block text-xs font-medium text-sand-600 dark:text-sand-400 mb-1" htmlFor="si-password">
331
331
  Password
332
332
  </label>
333
333
  <input
@@ -337,7 +337,7 @@ export function CheckoutSuccessClient({
337
337
  required
338
338
  value={password}
339
339
  onChange={(e) => setPassword(e.target.value)}
340
- className="w-full rounded-lg border border-white/10 bg-black/30 px-3 py-2 text-sm text-white placeholder-white/20 focus:border-[var(--brand-accent)] focus:outline-none"
340
+ className="w-full rounded-lg border border-sand-200 dark:border-sand-800 bg-sand-50 dark:bg-sand-950 px-3 py-2 text-sm text-sand-900 dark:text-sand-50 placeholder:text-sand-400 focus:border-[var(--brand-accent)] focus:outline-none"
341
341
  placeholder="Your password"
342
342
  />
343
343
  </div>
@@ -360,13 +360,13 @@ export function CheckoutSuccessClient({
360
360
  {/* Email verification step */}
361
361
  {step === "verify" && (
362
362
  <>
363
- <h2 className="text-base font-semibold text-white mb-1">Check your email</h2>
364
- <p className="text-xs text-white/40 mb-5">
365
- We sent a 6-digit code to <span className="text-white/70">{email}</span>.
363
+ <h2 className="text-base font-semibold text-sand-900 dark:text-sand-50 mb-1">Check your email</h2>
364
+ <p className="text-xs text-sand-500 mb-5">
365
+ We sent a 6-digit code to <span className="text-sand-600 dark:text-sand-300">{email}</span>.
366
366
  </p>
367
367
  <form onSubmit={handleVerify} className="space-y-4">
368
368
  <div>
369
- <label className="block text-xs font-medium text-white/60 mb-1" htmlFor="verify-code">
369
+ <label className="block text-xs font-medium text-sand-600 dark:text-sand-400 mb-1" htmlFor="verify-code">
370
370
  Verification code
371
371
  </label>
372
372
  <input
@@ -378,7 +378,7 @@ export function CheckoutSuccessClient({
378
378
  maxLength={6}
379
379
  value={code}
380
380
  onChange={(e) => setCode(e.target.value.replace(/\D/g, ""))}
381
- className="w-full rounded-lg border border-white/10 bg-black/30 px-3 py-2 text-center text-lg tracking-[0.4em] text-white placeholder-white/20 focus:border-[var(--brand-accent)] focus:outline-none"
381
+ className="w-full rounded-lg border border-sand-200 dark:border-sand-800 bg-sand-50 dark:bg-sand-950 px-3 py-2 text-center text-lg tracking-[0.4em] text-sand-900 dark:text-sand-50 placeholder:text-sand-400 focus:border-[var(--brand-accent)] focus:outline-none"
382
382
  placeholder="000000"
383
383
  />
384
384
  </div>
@@ -398,7 +398,7 @@ export function CheckoutSuccessClient({
398
398
  <button
399
399
  type="button"
400
400
  onClick={() => { setStep("credentials"); setError(null); setCode(""); }}
401
- className="mt-4 w-full text-center text-xs text-white/30 hover:text-white/50"
401
+ className="mt-4 w-full text-center text-xs text-sand-400 hover:text-sand-500"
402
402
  >
403
403
  ← Back
404
404
  </button>
@@ -406,7 +406,7 @@ export function CheckoutSuccessClient({
406
406
  )}
407
407
  </div>
408
408
 
409
- <p className="text-center text-xs text-white/30">
409
+ <p className="text-center text-xs text-sand-400">
410
410
  Your payment is confirmed. Account setup is required to receive your eSIM QR code.
411
411
  </p>
412
412
  </div>
@@ -43,7 +43,7 @@ export default async function CheckoutSuccessPage({ searchParams }: Props) {
43
43
  <div className="flex min-h-[calc(100vh-4rem)] items-center justify-center px-4">
44
44
  <div className="text-center space-y-3">
45
45
  <div className="mx-auto size-8 animate-spin rounded-full border-2 border-[var(--brand-accent)] border-t-transparent" />
46
- <p className="text-sm text-white/60">Loading…</p>
46
+ <p className="text-sm text-sand-600 dark:text-sand-400">Loading…</p>
47
47
  </div>
48
48
  </div>
49
49
  }
@@ -1,34 +1,42 @@
1
+ import Link from "next/link";
1
2
  import brand from "@/brand.config";
3
+ import { buildSupportLink } from "@/vendor/ui/brand";
2
4
 
3
5
  export const metadata = { title: "Contact" };
4
6
 
5
7
  export default function ContactPage() {
6
8
  return (
7
- <div className="mx-auto max-w-2xl px-4 py-24">
8
- <h1 className="text-3xl font-bold text-white mb-8">Get in touch</h1>
9
- <div className="space-y-4 text-white/70">
10
- <p>
11
- Email:{" "}
12
- <a
13
- href={`mailto:${brand.supportEmail}`}
14
- className="text-[var(--brand-accent)] hover:underline"
15
- >
16
- {brand.supportEmail}
17
- </a>
18
- </p>
19
- {brand.supportWhatsapp && (
20
- <p>
21
- WhatsApp:{" "}
22
- <a
23
- href={`https://wa.me/${brand.supportWhatsapp.replace(/[^0-9]/g, "")}`}
24
- target="_blank"
25
- rel="noopener noreferrer"
26
- className="text-[var(--brand-accent)] hover:underline"
27
- >
28
- {brand.supportWhatsapp}
29
- </a>
30
- </p>
31
- )}
9
+ <div className="mx-auto max-w-2xl px-4 py-16">
10
+ <h1 className="text-3xl font-semibold tracking-tight text-sand-900 dark:text-sand-50">
11
+ Get in touch
12
+ </h1>
13
+ <p className="mt-2 mb-10 text-sand-600 dark:text-sand-400">
14
+ Real humans. 24/7. Pick the channel you already have open.
15
+ </p>
16
+ <div className="space-y-3">
17
+ <Link
18
+ href={buildSupportLink("whatsapp", { message: `Hi ${brand.name}!` })}
19
+ target="_blank"
20
+ rel="noopener noreferrer"
21
+ className="flex items-center justify-between rounded-2xl border border-sand-200 bg-white px-5 py-4 text-sand-900 dark:border-sand-800 dark:bg-sand-900 dark:text-sand-50"
22
+ >
23
+ <span className="font-medium">WhatsApp</span>
24
+ <span className="text-sm text-sand-500">24/7</span>
25
+ </Link>
26
+ <Link
27
+ href={buildSupportLink("sms")}
28
+ className="flex items-center justify-between rounded-2xl border border-sand-200 bg-white px-5 py-4 text-sand-900 dark:border-sand-800 dark:bg-sand-900 dark:text-sand-50"
29
+ >
30
+ <span className="font-medium">SMS</span>
31
+ <span className="text-sm text-sand-500">{brand.supportWhatsapp}</span>
32
+ </Link>
33
+ <a
34
+ href={`mailto:${brand.supportEmail}`}
35
+ className="flex items-center justify-between rounded-2xl border border-sand-200 bg-white px-5 py-4 text-sand-900 dark:border-sand-800 dark:bg-sand-900 dark:text-sand-50"
36
+ >
37
+ <span className="font-medium">Email</span>
38
+ <span className="text-sm text-sand-500">{brand.supportEmail}</span>
39
+ </a>
32
40
  </div>
33
41
  </div>
34
42
  );
@@ -11,12 +11,15 @@ export default async function DashboardPage() {
11
11
 
12
12
  return (
13
13
  <div className="mx-auto max-w-4xl px-4 py-16">
14
- <h1 className="text-3xl font-bold text-white mb-8">Your eSIMs</h1>
15
- <div className="rounded-2xl border border-white/10 bg-white/5 p-12 text-center">
16
- <p className="text-white/50">No active eSIMs yet.</p>
14
+ <h1 className="mb-8 text-3xl font-semibold tracking-tight text-sand-900 dark:text-sand-50">
15
+ Your eSIMs
16
+ </h1>
17
+ <div className="rounded-2xl border border-sand-200 bg-white p-12 text-center dark:border-sand-800 dark:bg-sand-900">
18
+ <p className="text-sand-500">No active eSIMs yet.</p>
17
19
  <a
18
20
  href="/shop"
19
- className="mt-4 inline-block rounded-full bg-[var(--brand-accent)] px-6 py-2 text-sm font-semibold text-white hover:bg-[var(--brand-accent-dark)] transition-colors"
21
+ className="mt-4 inline-block rounded-full px-6 py-2 text-sm font-semibold text-white transition-opacity hover:opacity-90"
22
+ style={{ background: "var(--brand-accent)" }}
20
23
  >
21
24
  Browse plans
22
25
  </a>