diff --git a/apps/portal/public/assets/images/About us.png b/apps/portal/public/assets/images/about-us.png similarity index 100% rename from apps/portal/public/assets/images/About us.png rename to apps/portal/public/assets/images/about-us.png diff --git a/apps/portal/src/app/(public)/(site)/services/onsite/OnsiteSupportContent.tsx b/apps/portal/src/app/(public)/(site)/services/onsite/OnsiteSupportContent.tsx index 49deaab9..24aa335a 100644 --- a/apps/portal/src/app/(public)/(site)/services/onsite/OnsiteSupportContent.tsx +++ b/apps/portal/src/app/(public)/(site)/services/onsite/OnsiteSupportContent.tsx @@ -106,7 +106,7 @@ export function OnsiteSupportContent() { return (
{/* Hero Section */} -
+
{/* Dot grid pattern */}
{/* How It Works Section */} -
+

How It Works @@ -239,7 +239,7 @@ export function OnsiteSupportContent() {

{/* Pricing Cards Section */} -
+
{/* Subtle pattern overlay */}
{/* FAQ Section */} -
+

Frequently Asked Questions @@ -387,7 +387,7 @@ export function OnsiteSupportContent() {

{/* CTA Section */} -
+
diff --git a/apps/portal/src/components/molecules/ServiceCard/ServiceCard.tsx b/apps/portal/src/components/molecules/ServiceCard/ServiceCard.tsx index 0016a84d..095a9cde 100644 --- a/apps/portal/src/components/molecules/ServiceCard/ServiceCard.tsx +++ b/apps/portal/src/components/molecules/ServiceCard/ServiceCard.tsx @@ -35,8 +35,8 @@ export type ServiceCardVariant = | "bento-lg"; export interface ServiceCardProps { - /** Link destination */ - href: string; + /** Link destination (renders as div when omitted) */ + href?: string; /** * Icon element to display. * Pass a pre-styled JSX element: `icon={}` @@ -157,6 +157,28 @@ function renderIcon(icon: ReactNode, className: string): ReactNode { return icon; } +/** + * Shared wrapper that renders when href is provided,
otherwise + */ +function CardWrapper({ + href, + className, + children, +}: { + href?: string | undefined; + className?: string; + children: ReactNode; +}) { + if (href) { + return ( + + {children} + + ); + } + return
{children}
; +} + /** * Default variant - Standard service card */ @@ -173,7 +195,7 @@ function DefaultCard({ const colors = accentColorStyles[accentColor]; return ( - +
{description}

)} -
- Learn more - -
+ {href && ( +
+ Learn more + +
+ )}
- +
); } @@ -226,7 +250,7 @@ function DefaultCard({ */ function FeaturedCard({ href, icon, title, description, highlight, className }: ServiceCardProps) { return ( - +
- Learn more - -
+ {href && ( +
+ Learn more + +
+ )}
- + ); } @@ -290,7 +316,7 @@ function FeaturedCard({ href, icon, title, description, highlight, className }: */ function MinimalCard({ href, icon, title, className }: ServiceCardProps) { return ( - +

{title}

- +
); } @@ -317,7 +343,7 @@ function BentoSmallCard({ href, icon, title, accentColor = "blue", className }: const colors = accentColorStyles[accentColor]; return ( - {title}
- + ); } @@ -351,7 +377,7 @@ function BentoMediumCard({ const colors = accentColorStyles[accentColor]; return ( - {description}

)} - + ); } @@ -395,7 +421,7 @@ function BentoLargeCard({ const colors = accentColorStyles[accentColor]; return ( - {description}

)} - - Learn more - - + {href && ( + + Learn more + + + )}
- + ); } diff --git a/apps/portal/src/features/landing-page/components/CTABanner.tsx b/apps/portal/src/features/landing-page/components/CTABanner.tsx index 0b953d98..b1783f3f 100644 --- a/apps/portal/src/features/landing-page/components/CTABanner.tsx +++ b/apps/portal/src/features/landing-page/components/CTABanner.tsx @@ -3,10 +3,7 @@ import { Button } from "@/components/atoms/button"; export function CTABanner() { return ( -
+

Ready to Get Set Up? diff --git a/apps/portal/src/features/landing-page/components/ContactSection.tsx b/apps/portal/src/features/landing-page/components/ContactSection.tsx index 5d4b7c95..0f5397bb 100644 --- a/apps/portal/src/features/landing-page/components/ContactSection.tsx +++ b/apps/portal/src/features/landing-page/components/ContactSection.tsx @@ -13,7 +13,7 @@ export function ContactSection() { id="contact" ref={ref} className={cn( - "relative left-1/2 right-1/2 w-screen -translate-x-1/2 bg-surface-sunken/30 py-14 sm:py-16 transition-all duration-700", + "full-bleed bg-surface-sunken/30 py-14 sm:py-16 transition-all duration-700", isInView ? "opacity-100 translate-y-0" : "opacity-0 translate-y-8" )} > diff --git a/apps/portal/src/features/landing-page/components/HeroSection.tsx b/apps/portal/src/features/landing-page/components/HeroSection.tsx index 589f0254..0ce10666 100644 --- a/apps/portal/src/features/landing-page/components/HeroSection.tsx +++ b/apps/portal/src/features/landing-page/components/HeroSection.tsx @@ -16,7 +16,7 @@ export function HeroSection({ heroCTARef }: HeroSectionProps) {
diff --git a/apps/portal/src/features/landing-page/components/ServicesCarousel.tsx b/apps/portal/src/features/landing-page/components/ServicesCarousel.tsx index e7de4716..d621b2d2 100644 --- a/apps/portal/src/features/landing-page/components/ServicesCarousel.tsx +++ b/apps/portal/src/features/landing-page/components/ServicesCarousel.tsx @@ -299,7 +299,7 @@ export function ServicesCarousel() {
diff --git a/apps/portal/src/features/landing-page/components/TrustStrip.tsx b/apps/portal/src/features/landing-page/components/TrustStrip.tsx index e295b4bb..99173836 100644 --- a/apps/portal/src/features/landing-page/components/TrustStrip.tsx +++ b/apps/portal/src/features/landing-page/components/TrustStrip.tsx @@ -73,7 +73,7 @@ export function TrustStrip() { ref={ref} aria-label="Company statistics" className={cn( - "relative left-1/2 right-1/2 w-screen -translate-x-1/2 py-10 sm:py-12 overflow-hidden transition-all duration-700", + "full-bleed py-10 sm:py-12 overflow-hidden transition-all duration-700", inView ? "opacity-100 translate-y-0" : "opacity-0 translate-y-8" )} > diff --git a/apps/portal/src/features/landing-page/components/WhyUsSection.tsx b/apps/portal/src/features/landing-page/components/WhyUsSection.tsx index 16b59779..5825a580 100644 --- a/apps/portal/src/features/landing-page/components/WhyUsSection.tsx +++ b/apps/portal/src/features/landing-page/components/WhyUsSection.tsx @@ -19,7 +19,7 @@ export function WhyUsSection() {
diff --git a/apps/portal/src/features/landing-page/views/PublicLandingLoadingView.tsx b/apps/portal/src/features/landing-page/views/PublicLandingLoadingView.tsx index 6eb2d7fe..3c04a5b5 100644 --- a/apps/portal/src/features/landing-page/views/PublicLandingLoadingView.tsx +++ b/apps/portal/src/features/landing-page/views/PublicLandingLoadingView.tsx @@ -4,7 +4,7 @@ export function PublicLandingLoadingView() { return (
{/* Hero Section Skeleton */} -
+
@@ -22,7 +22,7 @@ export function PublicLandingLoadingView() {
{/* TrustStrip Skeleton */} -
+
{Array.from({ length: 4 }).map((_, idx) => ( @@ -72,7 +72,7 @@ export function PublicLandingLoadingView() {
{/* CTABanner Skeleton */} -
+
diff --git a/apps/portal/src/features/marketing/views/AboutUsView.tsx b/apps/portal/src/features/marketing/views/AboutUsView.tsx index aff40126..09330dd7 100644 --- a/apps/portal/src/features/marketing/views/AboutUsView.tsx +++ b/apps/portal/src/features/marketing/views/AboutUsView.tsx @@ -1,4 +1,5 @@ import Image from "next/image"; +import { ServiceCard } from "@/components/molecules"; import type { LucideIcon } from "lucide-react"; import { Wifi, @@ -21,34 +22,39 @@ import { /* ─── Data ─── */ -const services: { title: string; description: string; icon: LucideIcon }[] = [ +const services = [ { title: "Internet Plans", description: "High-speed NTT fiber with English support. We handle the Japanese paperwork so you don't have to.", - icon: Wifi, + icon: , + accentColor: "blue" as const, }, { title: "Phone Plans", description: "SIM cards on Japan's best network. Foreign credit cards accepted, no hanko required.", - icon: Smartphone, + icon: , + accentColor: "green" as const, }, { title: "Business Solutions", description: "Enterprise IT for international companies. Dedicated internet, office networks, and data centers.", - icon: Building2, + icon: , + accentColor: "purple" as const, }, { title: "VPN", description: "Stream your favorite shows from home. Pre-configured router for US/UK content.", - icon: Lock, + icon: , + accentColor: "orange" as const, }, { title: "Onsite Support", description: "English-speaking technicians at your door for setup and troubleshooting.", - icon: Wrench, + icon: , + accentColor: "cyan" as const, }, ]; @@ -111,10 +117,6 @@ const businessHours = [ { team: "Onsite Tech Support Team", hours: "Mon \u2013 Sat 10:00AM \u2013 9:00PM" }, ]; -/* ─── Full-width section wrapper ─── */ - -const SECTION_BASE = "relative left-1/2 right-1/2 w-screen -translate-x-1/2"; - const HERO_GLOW_STYLE = { background: "radial-gradient(circle, oklch(0.7 0.12 234.4 / 0.35), transparent 70%)", } as const; @@ -123,7 +125,7 @@ const HERO_GLOW_STYLE = { function HeroSection() { return ( -
+
Assist Solutions team in Tokyo +

What We Do

@@ -179,23 +181,16 @@ function ServicesSection() {
- {services.map(service => { - const Icon = service.icon; - return ( -
-
- -
-

{service.title}

-

- {service.description} -

-
- ); - })} + {services.map(service => ( + + ))}
@@ -204,7 +199,7 @@ function ServicesSection() { function ValuesSection() { return ( -
+

Our Values

@@ -242,7 +237,7 @@ function ValuesSection() { function CorporateSection() { return ( -
+

Corporate Data

diff --git a/apps/portal/src/features/services/components/base/CollapsibleSection.tsx b/apps/portal/src/features/services/components/base/CollapsibleSection.tsx new file mode 100644 index 00000000..81205042 --- /dev/null +++ b/apps/portal/src/features/services/components/base/CollapsibleSection.tsx @@ -0,0 +1,50 @@ +"use client"; + +import { useState, type ElementType, type ReactNode } from "react"; +import { ChevronDown } from "lucide-react"; +import { cn } from "@/shared/utils"; + +interface CollapsibleSectionProps { + title: string; + icon: ElementType; + defaultOpen?: boolean; + children: ReactNode; +} + +export function CollapsibleSection({ + title, + icon: Icon, + defaultOpen = false, + children, +}: CollapsibleSectionProps) { + const [isOpen, setIsOpen] = useState(defaultOpen); + + return ( +
+ +
+
{children}
+
+
+ ); +} diff --git a/apps/portal/src/features/services/components/base/ServiceCTA.tsx b/apps/portal/src/features/services/components/base/ServiceCTA.tsx index 757a1292..5414debd 100644 --- a/apps/portal/src/features/services/components/base/ServiceCTA.tsx +++ b/apps/portal/src/features/services/components/base/ServiceCTA.tsx @@ -24,6 +24,11 @@ export interface ServiceCTAProps { className?: string | undefined; } +const dotPatternStyle = { + backgroundImage: `radial-gradient(circle at center, color-mix(in oklch, var(--primary) 12%, transparent) 0.6px, transparent 0.6px)`, + backgroundSize: "20px 20px", +} as const; + /** * ServiceCTA - Call-to-action section with decorative background. */ @@ -42,13 +47,7 @@ export function ServiceCTA({
{/* Refined dot pattern */} -
+
{/* Content */}
diff --git a/apps/portal/src/features/services/components/base/ServiceFAQ.tsx b/apps/portal/src/features/services/components/base/ServiceFAQ.tsx index f612a5c7..62262951 100644 --- a/apps/portal/src/features/services/components/base/ServiceFAQ.tsx +++ b/apps/portal/src/features/services/components/base/ServiceFAQ.tsx @@ -1,12 +1,12 @@ "use client"; -import { useState } from "react"; +import { useState, type ReactNode } from "react"; import { ChevronDown } from "lucide-react"; import { cn } from "@/shared/utils/cn"; export interface FAQItem { question: string; - answer: string; + answer: ReactNode; } export interface ServiceFAQProps { @@ -24,7 +24,7 @@ function FAQItemComponent({ onToggle, }: { question: string; - answer: string; + answer: ReactNode; isOpen: boolean; onToggle: () => void; }) { @@ -53,7 +53,7 @@ function FAQItemComponent({ )} >
-

{answer}

+
{answer}
diff --git a/apps/portal/src/features/services/components/sim/SimPlansContent.tsx b/apps/portal/src/features/services/components/sim/SimPlansContent.tsx index bd415212..3ad25d98 100644 --- a/apps/portal/src/features/services/components/sim/SimPlansContent.tsx +++ b/apps/portal/src/features/services/components/sim/SimPlansContent.tsx @@ -1,6 +1,6 @@ "use client"; -import { useMemo, useState, type ElementType, type ReactNode } from "react"; +import { useMemo } from "react"; import { Smartphone, Check, @@ -10,7 +10,6 @@ import { Signal, Sparkles, CreditCard, - ChevronDown, Info, CircleDollarSign, TriangleAlert, @@ -27,6 +26,8 @@ import { ServicesBackLink } from "@/features/services/components/base/ServicesBa import { ServicesHero } from "@/features/services/components/base/ServicesHero"; import { useServicesBasePath } from "@/features/services/hooks/useServicesBasePath"; import { CardPricing } from "@/features/services/components/base/CardPricing"; +import { CollapsibleSection } from "@/features/services/components/base/CollapsibleSection"; +import { ServiceFAQ, type FAQItem } from "@/features/services/components/base/ServiceFAQ"; import { DeviceCompatibility } from "./DeviceCompatibility"; import { ServiceHighlights, @@ -43,48 +44,110 @@ interface PlansByType { VoiceOnly: SimCatalogProduct[]; } -function CollapsibleSection({ - title, - icon: Icon, - defaultOpen = false, - children, -}: { - title: string; - icon: ElementType; - defaultOpen?: boolean; - children: ReactNode; -}) { - const [isOpen, setIsOpen] = useState(defaultOpen); +const simFeatures: HighlightFeature[] = [ + { + icon: , + title: "Foreign Cards Accepted", + description: "Use your overseas credit card. No Japanese bank account needed", + highlight: "Easy payment", + }, + { + icon: , + title: "Japan's Best Network", + description: "NTT Docomo coverage reaches 99.9% of Japan's population", + highlight: "Nationwide", + }, + { + icon: , + title: "First Month Free", + description: "Try risk-free. Your first month's basic fee is waived", + highlight: "Great value", + }, + { + icon: , + title: "Flexible Terms", + description: "No multi-year contracts. Stay as long as you need", + highlight: "No lock-in", + }, + { + icon: , + title: "Keep Your Number", + description: "Switching from another carrier? Bring your Japanese number with you", + highlight: "Easy transfer", + }, + { + icon: , + title: "Free Plan Changes", + description: "Switch data plans anytime for the next billing cycle", + highlight: "Flexibility", + }, +]; - return ( -
- -
-
{children}
-
-
- ); -} +const SIM_TABS = [ + { + key: "data-voice" as const, + icon: Phone, + label: "Data + Voice", + shortLabel: "All-in", + planTypeKey: "DataSmsVoice" as const, + }, + { + key: "data-only" as const, + icon: Globe, + label: "Data Only", + shortLabel: "Data", + planTypeKey: "DataOnly" as const, + }, + { + key: "voice-only" as const, + icon: Check, + label: "Voice Only", + shortLabel: "Voice", + planTypeKey: "VoiceOnly" as const, + }, +]; + +const SIM_FAQ_ITEMS: FAQItem[] = [ + { + question: "What is the service contract period?", + answer: + "The minimum service requirement period is activation month + 3 billing months. After this period, the service will switch to a monthly service and you will be able to cancel at the end of each month.", + }, + { + question: "I've changed my phone and the SIM card is not working on the new device.", + answer: + "Whenever the SIM card is used with a new device, the APN profile would need to be installed on said device. Please refer to the APN Setup Guide in the Documents section to check how the profile can be installed.", + }, + { + question: "Are international calling features available?", + answer: ( + <> + Enter "+" or "010", "recipient's country code", and + "recipient's phone number (regular phone number/mobile phone number)" → Make + a call. +
+
+ If the recipient's phone number begins with a 0, enter it without the first 0 (except + in some countries and regions). +
+ International calling rate is on the following Docomo's website:{" "} + + Docomo International Calling Rates + + + ), + }, + { + question: "How do I cancel the service?", + answer: + "To cancel, please log into the SonixNet SIM Management Website and send us a cancellation request before the 25th to cancel your account at the end of the month. For example, cancellation requests will need to be sent in by May 25th, in order to cancel at the end of May.", + }, +]; function SimPlanCardCompact({ plan, @@ -187,45 +250,6 @@ export function SimPlansContent({ const simPlans: SimCatalogProduct[] = useMemo(() => plans ?? [], [plans]); const hasExistingSim = useMemo(() => simPlans.some(p => p.simHasFamilyDiscount), [simPlans]); - const simFeatures: HighlightFeature[] = [ - { - icon: , - title: "Foreign Cards Accepted", - description: "Use your overseas credit card. No Japanese bank account needed", - highlight: "Easy payment", - }, - { - icon: , - title: "Japan's Best Network", - description: "NTT Docomo coverage reaches 99.9% of Japan's population", - highlight: "Nationwide", - }, - { - icon: , - title: "First Month Free", - description: "Try risk-free. Your first month's basic fee is waived", - highlight: "Great value", - }, - { - icon: , - title: "Flexible Terms", - description: "No multi-year contracts. Stay as long as you need", - highlight: "No lock-in", - }, - { - icon: , - title: "Keep Your Number", - description: "Switching from another carrier? Bring your Japanese number with you", - highlight: "Easy transfer", - }, - { - icon: , - title: "Free Plan Changes", - description: "Switch data plans anytime for the next billing cycle", - highlight: "Flexibility", - }, - ]; - if (isLoading) { return (
@@ -324,29 +348,7 @@ export function SimPlansContent({ {/* Tab Switcher */}
- {[ - { - key: "data-voice" as const, - icon: Phone, - label: "Data + Voice", - shortLabel: "All-in", - count: plansByType.DataSmsVoice.length, - }, - { - key: "data-only" as const, - icon: Globe, - label: "Data Only", - shortLabel: "Data", - count: plansByType.DataOnly.length, - }, - { - key: "voice-only" as const, - icon: Check, - label: "Voice Only", - shortLabel: "Voice", - count: plansByType.VoiceOnly.length, - }, - ].map(tab => ( + {SIM_TABS.map(tab => ( ))} @@ -631,54 +633,7 @@ export function SimPlansContent({
{/* FAQ Section */} -
-
-

- Common Questions -

-

- Frequently Asked Questions -

-
-
- - - - Enter "+" or "010", "recipient's country code", - and "recipient's phone number (regular phone number/mobile phone - number)" → Make a call. -
-
- If the recipient's phone number begins with a 0, enter it without the first 0 - (except in some countries and regions). -
- International calling rate is on the following Docomo's website:{" "} - - Docomo International Calling Rates - - - } - /> - -
-
+ {/* Footer */}
@@ -693,31 +648,4 @@ export function SimPlansContent({ ); } -function FaqItem({ question, answer }: { question: string; answer: React.ReactNode }) { - const [isOpen, setIsOpen] = useState(false); - - return ( -
- - {isOpen && ( -
{answer}
- )} -
- ); -} - export default SimPlansContent; diff --git a/apps/portal/src/features/services/components/vpn/VpnPlansContent.tsx b/apps/portal/src/features/services/components/vpn/VpnPlansContent.tsx index d0a010cd..2c6112bb 100644 --- a/apps/portal/src/features/services/components/vpn/VpnPlansContent.tsx +++ b/apps/portal/src/features/services/components/vpn/VpnPlansContent.tsx @@ -1,6 +1,7 @@ "use client"; import { ShieldCheck, Zap, CreditCard, Play, Globe, Package, ArrowLeft } from "lucide-react"; +import { ServicesHero } from "@/features/services/components/base/ServicesHero"; import { Skeleton } from "@/components/atoms/loading-skeleton"; import { Button } from "@/components/atoms/button"; import { AlertBanner } from "@/components/molecules/AlertBanner/AlertBanner"; @@ -135,35 +136,18 @@ export function VpnPlansContent({ {/* Hero */} -
-
- + VPN Router Service -
-

- Stream Content from Abroad -

-

- Access US and UK streaming services using a pre-configured VPN router. No technical setup - required. -

- + } + > {variant === "public" && ( -
+
@@ -178,7 +162,7 @@ export function VpnPlansContent({
)} -
+ {/* Highlights */} diff --git a/apps/portal/src/styles/utilities.css b/apps/portal/src/styles/utilities.css index 26a4ee4c..8c261a96 100644 --- a/apps/portal/src/styles/utilities.css +++ b/apps/portal/src/styles/utilities.css @@ -142,6 +142,14 @@ font-family are explicit Tailwind classes at the call site so they can be overridden without cascade conflicts. */ +@utility full-bleed { + position: relative; + left: 50%; + right: 50%; + width: 100vw; + transform: translateX(-50%); +} + @utility text-display-xl { font-size: var(--cp-text-display-xl); letter-spacing: var(--cp-tracking-tight);