refactor: layer portal routes through feature modules
This commit is contained in:
parent
143aad11d8
commit
e9acbd899c
@ -6,26 +6,31 @@ This document outlines the new feature-driven architecture implemented for the c
|
||||
|
||||
```
|
||||
apps/portal/src/
|
||||
├── app/ # Next.js App Router pages
|
||||
├── components/ # Shared UI components (Design System)
|
||||
│ ├── ui/ # Base UI components (atoms)
|
||||
│ ├── layout/ # Layout components (organisms)
|
||||
│ └── common/ # Shared business components (molecules)
|
||||
├── features/ # Feature-specific modules
|
||||
│ ├── auth/ # Authentication feature
|
||||
│ ├── dashboard/ # Dashboard feature
|
||||
│ ├── billing/ # Billing feature
|
||||
│ ├── subscriptions/ # Subscriptions feature
|
||||
│ ├── catalog/ # Product catalog feature
|
||||
│ └── support/ # Support feature
|
||||
├── lib/ # Core utilities and services (replaces core/shared)
|
||||
│ ├── api/ # API client and base services
|
||||
│ ├── query.ts # Query client and keys
|
||||
│ ├── env.ts # Runtime env parsing
|
||||
│ ├── types/ # Shared TypeScript types
|
||||
│ └── utils/ # Utility functions (cn, currency, error-display, ...)
|
||||
├── providers/ # React context providers (e.g., QueryProvider)
|
||||
└── styles/ # Global styles and design tokens
|
||||
├── app/ # Next.js App Router entry points (route groups only)
|
||||
│ ├── (public)/ # Marketing + auth routes, pages import feature views
|
||||
│ ├── (authenticated)/ # Signed-in portal routes, thin wrappers around features
|
||||
│ ├── api/ # App Router API routes
|
||||
│ ├── favicon.ico / globals.css # Global assets
|
||||
│ └── layout.tsx # Root layout/providers
|
||||
├── components/ # Shared UI components (design system atoms/molecules)
|
||||
│ ├── ui/
|
||||
│ ├── layout/
|
||||
│ └── common/
|
||||
├── core/ # App-wide configuration (env, logger, providers)
|
||||
├── features/ # Feature-specific modules composed by routes
|
||||
│ ├── account/
|
||||
│ ├── auth/
|
||||
│ ├── billing/
|
||||
│ ├── catalog/
|
||||
│ ├── dashboard/
|
||||
│ ├── marketing/
|
||||
│ ├── orders/
|
||||
│ ├── service-management/
|
||||
│ ├── subscriptions/
|
||||
│ └── support/
|
||||
├── shared/ # Cross-feature helpers (e.g., constants, locale data)
|
||||
├── styles/ # Global styles and design tokens
|
||||
└── types/ # Portal-specific TypeScript types
|
||||
```
|
||||
|
||||
## Design Principles
|
||||
@ -48,7 +53,7 @@ Each feature module contains:
|
||||
- `utils/`: Utility functions
|
||||
|
||||
### 4. Centralized Shared Resources
|
||||
Common utilities, types, and components are centralized in the `lib/` and `components/` directories.
|
||||
Common utilities, types, and components are centralized in the `core/`, `shared/`, and `components/` directories.
|
||||
|
||||
## Feature Module Structure
|
||||
|
||||
@ -118,20 +123,21 @@ import { DataTable } from '@/components/common';
|
||||
import type { User, ApiResponse } from '@/types';
|
||||
|
||||
// Utility imports
|
||||
import { designSystem } from '@/lib/design-system';
|
||||
// Prefer feature services/hooks over direct apiClient usage in pages
|
||||
import { apiClient } from '@/lib/api/client';
|
||||
import { QueryProvider } from '@/core/providers';
|
||||
// Prefer feature services/hooks over direct api usage in pages
|
||||
import { logger } from '@/core/config';
|
||||
```
|
||||
|
||||
### Path Mappings
|
||||
|
||||
- `@/*` - Root src directory
|
||||
- `@/components/*` - Component library
|
||||
- `@/core/*` - App-wide configuration and providers
|
||||
- `@/features/*` - Feature modules
|
||||
- `@/lib/*` - Core utilities
|
||||
- `@/types` - Type definitions
|
||||
- `@/shared/*` - Shared helpers/constants
|
||||
- `@/styles/*` - Style files
|
||||
- `@shared/*` - Shared package
|
||||
- `@/types/*` - Portal-specific types
|
||||
- `@shared/*` - Shared package exports
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
@ -179,6 +185,14 @@ The migration to this new architecture will be done incrementally:
|
||||
|
||||
This ensures pages remain declarative and the feature layer encapsulates logic.
|
||||
|
||||
### Route Layering
|
||||
|
||||
- `(public)`: marketing landing and authentication flows. These routes render feature views such as `marketing/PublicLandingView` and `auth` screens while remaining server components by default.
|
||||
- `(authenticated)`: signed-in portal experience. Pages import dashboard, billing, subscriptions, etc. from the feature layer and rely on the shared route-group layout to provide navigation.
|
||||
- `api/`: App Router API endpoints remain colocated under `src/app/api` and can reuse feature services for data access.
|
||||
|
||||
Only `layout.tsx`, `page.tsx`, and `loading.tsx` files live inside the route groups. All reusable UI, hooks, and services live under `src/features/**` to keep routing concerns thin.
|
||||
|
||||
### Current Feature Hooks/Services
|
||||
|
||||
- Catalog
|
||||
@ -191,3 +205,7 @@ This ensures pages remain declarative and the feature layer encapsulates logic.
|
||||
- Service: `ordersService` (list/detail/create)
|
||||
- Account
|
||||
- Service: `accountService` (`/me/address`)
|
||||
- Support
|
||||
- Views: `SupportCasesView`, `NewSupportCaseView` (mock data, ready for API wiring)
|
||||
- Marketing
|
||||
- Views: `PublicLandingView`, `PublicLandingLoadingView`
|
||||
|
||||
5
apps/portal/src/app/(authenticated)/dashboard/page.tsx
Normal file
5
apps/portal/src/app/(authenticated)/dashboard/page.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import { DashboardView } from "@/features/dashboard";
|
||||
|
||||
export default function DashboardPage() {
|
||||
return <DashboardView />;
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
import { SupportCasesView } from "@/features/support";
|
||||
|
||||
export default function SupportCasesPage() {
|
||||
return <SupportCasesView />;
|
||||
}
|
||||
5
apps/portal/src/app/(authenticated)/support/new/page.tsx
Normal file
5
apps/portal/src/app/(authenticated)/support/new/page.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import { NewSupportCaseView } from "@/features/support";
|
||||
|
||||
export default function NewSupportCasePage() {
|
||||
return <NewSupportCaseView />;
|
||||
}
|
||||
@ -1,260 +0,0 @@
|
||||
"use client";
|
||||
import { logger } from "@/core/config";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
ArrowLeftIcon,
|
||||
PaperAirplaneIcon,
|
||||
ExclamationCircleIcon,
|
||||
InformationCircleIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
|
||||
export default function NewSupportCasePage() {
|
||||
const router = useRouter();
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [formData, setFormData] = useState({
|
||||
subject: "",
|
||||
category: "Technical",
|
||||
priority: "Medium",
|
||||
description: "",
|
||||
});
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setIsSubmitting(true);
|
||||
|
||||
// Mock submission - would normally send to API
|
||||
void (async () => {
|
||||
try {
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
// Redirect to cases list with success message
|
||||
router.push("/support/cases?created=true");
|
||||
} catch (error) {
|
||||
logger.error(error, "Error creating case");
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
const handleInputChange = (field: string, value: string) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
[field]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
const isFormValid = formData.subject.trim() && formData.description.trim();
|
||||
|
||||
return (
|
||||
<div className="py-6">
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 md:px-8">
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center space-x-4 mb-4">
|
||||
<button
|
||||
onClick={() => router.back()}
|
||||
className="inline-flex items-center text-sm text-gray-500 hover:text-gray-700"
|
||||
>
|
||||
<ArrowLeftIcon className="h-4 w-4 mr-1" />
|
||||
Back to Support
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900">Create Support Case</h1>
|
||||
<p className="mt-1 text-sm text-gray-600">Get help from our support team</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Help Tips */}
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
<InformationCircleIcon className="h-5 w-5 text-blue-400" />
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<h3 className="text-sm font-medium text-blue-800">Before creating a case</h3>
|
||||
<div className="mt-2 text-sm text-blue-700">
|
||||
<ul className="list-disc list-inside space-y-1">
|
||||
<li>Check our knowledge base for common solutions</li>
|
||||
<li>Include relevant error messages or screenshots</li>
|
||||
<li>Provide detailed steps to reproduce the issue</li>
|
||||
<li>Mention your service or subscription if applicable</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Form */}
|
||||
<div className="bg-white shadow rounded-lg">
|
||||
<form onSubmit={handleSubmit} className="p-6 space-y-6">
|
||||
{/* Subject */}
|
||||
<div>
|
||||
<label htmlFor="subject" className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Subject *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="subject"
|
||||
value={formData.subject}
|
||||
onChange={e => handleInputChange("subject", e.target.value)}
|
||||
placeholder="Brief description of your issue"
|
||||
className="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Category and Priority */}
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
||||
<div>
|
||||
<label
|
||||
htmlFor="category"
|
||||
className="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
Category
|
||||
</label>
|
||||
<select
|
||||
id="category"
|
||||
value={formData.category}
|
||||
onChange={e => handleInputChange("category", e.target.value)}
|
||||
className="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
|
||||
>
|
||||
<option value="Technical">Technical Support</option>
|
||||
<option value="Billing">Billing Question</option>
|
||||
<option value="General">General Inquiry</option>
|
||||
<option value="Feature Request">Feature Request</option>
|
||||
<option value="Bug Report">Bug Report</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="priority"
|
||||
className="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
Priority
|
||||
</label>
|
||||
<select
|
||||
id="priority"
|
||||
value={formData.priority}
|
||||
onChange={e => handleInputChange("priority", e.target.value)}
|
||||
className="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
|
||||
>
|
||||
<option value="Low">Low - General question</option>
|
||||
<option value="Medium">Medium - Issue affecting work</option>
|
||||
<option value="High">High - Service disruption</option>
|
||||
<option value="Critical">Critical - Complete outage</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<div>
|
||||
<label
|
||||
htmlFor="description"
|
||||
className="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
Description *
|
||||
</label>
|
||||
<textarea
|
||||
id="description"
|
||||
rows={6}
|
||||
value={formData.description}
|
||||
onChange={e => handleInputChange("description", e.target.value)}
|
||||
placeholder="Please provide a detailed description of your issue, including any error messages and steps to reproduce the problem..."
|
||||
className="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500"
|
||||
required
|
||||
/>
|
||||
<p className="mt-2 text-xs text-gray-500">
|
||||
The more details you provide, the faster we can help you.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Priority Warning */}
|
||||
{formData.priority === "Critical" && (
|
||||
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
<ExclamationCircleIcon className="h-5 w-5 text-red-400" />
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<h3 className="text-sm font-medium text-red-800">
|
||||
Critical Priority Selected
|
||||
</h3>
|
||||
<div className="mt-2 text-sm text-red-700">
|
||||
<p>
|
||||
Critical priority should only be used for complete service outages. For
|
||||
urgent issues that aren't complete outages, please use High priority.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center justify-between pt-6 border-t border-gray-200">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.back()}
|
||||
className="inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!isFormValid || isSubmitting}
|
||||
className="inline-flex items-center px-6 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 disabled:bg-gray-300 disabled:cursor-not-allowed"
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
|
||||
Creating Case...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<PaperAirplaneIcon className="h-4 w-4 mr-2" />
|
||||
Create Case
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{/* Additional Help */}
|
||||
<div className="mt-8 bg-gray-50 rounded-lg p-6">
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-4">Need immediate help?</h3>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-gray-900">Phone Support</h4>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
9:30-18:00 JST
|
||||
<br />
|
||||
<span className="font-medium text-blue-600">0120-660-470</span>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-gray-900">Knowledge Base</h4>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
Search our help articles for quick solutions
|
||||
<br />
|
||||
<Link
|
||||
href="/support/kb"
|
||||
className="font-medium text-blue-600 hover:text-blue-500"
|
||||
>
|
||||
Browse Knowledge Base
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
5
apps/portal/src/app/(public)/loading.tsx
Normal file
5
apps/portal/src/app/(public)/loading.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import { PublicLandingLoadingView } from "@/features/marketing";
|
||||
|
||||
export default function PublicHomeLoading() {
|
||||
return <PublicLandingLoadingView />;
|
||||
}
|
||||
5
apps/portal/src/app/(public)/page.tsx
Normal file
5
apps/portal/src/app/(public)/page.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import { PublicLandingView } from "@/features/marketing";
|
||||
|
||||
export default function PublicHomePage() {
|
||||
return <PublicLandingView />;
|
||||
}
|
||||
@ -1,288 +0,0 @@
|
||||
import Link from "next/link";
|
||||
import { Logo } from "@/components/ui/logo";
|
||||
import {
|
||||
ArrowPathIcon,
|
||||
UserIcon,
|
||||
SparklesIcon,
|
||||
CreditCardIcon,
|
||||
Cog6ToothIcon,
|
||||
PhoneIcon,
|
||||
ChartBarIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-blue-100 to-blue-900">
|
||||
{/* Header */}
|
||||
<header className="bg-white/90 backdrop-blur-sm shadow-sm border-b border-blue-100">
|
||||
<div className="max-w-[var(--cp-page-max-width)] mx-auto px-[var(--cp-page-padding)] sm:px-6 lg:px-8">
|
||||
<div className="flex justify-between items-center h-16">
|
||||
<div className="flex items-center space-x-3">
|
||||
<Logo size={40} />
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold text-gray-900">Assist Solutions</h1>
|
||||
<p className="text-xs text-gray-500">Customer Portal</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-3">
|
||||
<Link
|
||||
href="/auth/login"
|
||||
className="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700 transition-colors duration-200 font-medium text-sm"
|
||||
>
|
||||
Login
|
||||
</Link>
|
||||
<Link
|
||||
href="/support"
|
||||
className="text-gray-600 hover:text-gray-900 px-4 py-2 text-sm transition-colors duration-200"
|
||||
>
|
||||
Support
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Hero Section */}
|
||||
<section className="relative py-20 overflow-hidden">
|
||||
{/* Abstract background elements */}
|
||||
<div className="absolute inset-0">
|
||||
<div className="absolute top-20 left-10 w-72 h-72 bg-blue-300 rounded-full mix-blend-multiply filter blur-xl opacity-20 animate-pulse"></div>
|
||||
<div
|
||||
className="absolute top-40 right-10 w-72 h-72 bg-indigo-300 rounded-full mix-blend-multiply filter blur-xl opacity-20 animate-pulse"
|
||||
style={{ animationDelay: "2s" }}
|
||||
></div>
|
||||
<div
|
||||
className="absolute -bottom-8 left-20 w-72 h-72 bg-purple-300 rounded-full mix-blend-multiply filter blur-xl opacity-20 animate-pulse"
|
||||
style={{ animationDelay: "4s" }}
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div className="relative max-w-[var(--cp-page-max-width)] mx-auto px-[var(--cp-page-padding)] sm:px-6 lg:px-8">
|
||||
<div className="text-center">
|
||||
<h1 className="text-5xl md:text-6xl font-bold text-gray-900 mb-[var(--cp-space-2xl)]">
|
||||
New Assist Solutions Customer Portal
|
||||
</h1>
|
||||
|
||||
<div className="flex justify-center">
|
||||
<a
|
||||
href="#portal-access"
|
||||
className="bg-blue-600 text-white px-10 py-4 rounded-xl hover:bg-blue-700 transition-all duration-300 font-semibold text-lg shadow-lg hover:shadow-xl transform hover:-translate-y-1"
|
||||
>
|
||||
Get Started
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Customer Portal Access Section */}
|
||||
<section id="portal-access" className="py-20">
|
||||
<div className="max-w-[var(--cp-page-max-width)] mx-auto px-[var(--cp-page-padding)] sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-3xl font-bold text-gray-900 mb-4">Access Your Portal</h2>
|
||||
<p className="text-lg text-gray-600">Choose the option that applies to you</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-[var(--cp-space-3xl)] items-stretch">
|
||||
{/* Existing Customers - Migration */}
|
||||
<div className="bg-white rounded-[var(--cp-card-radius-lg)] p-[var(--cp-space-3xl)] shadow-[var(--cp-card-shadow)] hover:shadow-[var(--cp-card-shadow-lg)] transition-all duration-[var(--cp-transition-slow)] transform hover:-translate-y-2 border-[var(--cp-card-border)]">
|
||||
<div className="text-center flex-1 flex flex-col">
|
||||
<div className="flex items-center justify-center w-16 h-16 bg-blue-600 rounded-full mb-6 mx-auto">
|
||||
<ArrowPathIcon className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-4">Existing Customers</h3>
|
||||
<p className="text-gray-600 mb-6 leading-relaxed">
|
||||
Migrate to our new portal and enjoy enhanced security with modern interface.
|
||||
</p>
|
||||
|
||||
<div className="mt-auto">
|
||||
<Link
|
||||
href="/auth/link-whmcs"
|
||||
className="block bg-blue-600 text-white px-8 py-4 rounded-xl hover:bg-blue-700 transition-all duration-300 font-semibold text-lg mb-3 shadow-md hover:shadow-lg"
|
||||
>
|
||||
Migrate Your Account
|
||||
</Link>
|
||||
<p className="text-sm text-gray-500">Takes just a few minutes</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Portal Users */}
|
||||
<div className="bg-white rounded-2xl p-8 shadow-lg hover:shadow-xl transition-all duration-300 transform hover:-translate-y-2 border border-gray-100">
|
||||
<div className="text-center flex-1 flex flex-col">
|
||||
<div className="flex items-center justify-center w-16 h-16 bg-blue-600 rounded-full mb-6 mx-auto">
|
||||
<UserIcon className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-4">Portal Users</h3>
|
||||
<p className="text-gray-600 mb-6 leading-relaxed">
|
||||
Sign in to access your dashboard and manage all your services efficiently.
|
||||
</p>
|
||||
|
||||
<div className="mt-auto">
|
||||
<Link
|
||||
href="/auth/login"
|
||||
className="block border-2 border-blue-600 text-blue-600 px-8 py-4 rounded-xl hover:bg-blue-600 hover:text-white transition-all duration-300 font-semibold text-lg mb-3"
|
||||
>
|
||||
Login to Portal
|
||||
</Link>
|
||||
<p className="text-sm text-gray-500">Secure access to your account</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* New Customers */}
|
||||
<div className="bg-white rounded-2xl p-8 shadow-lg hover:shadow-xl transition-all duration-300 transform hover:-translate-y-2 border border-gray-100">
|
||||
<div className="text-center flex-1 flex flex-col">
|
||||
<div className="flex items-center justify-center w-16 h-16 bg-blue-600 rounded-full mb-6 mx-auto">
|
||||
<SparklesIcon className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-4">New Customers</h3>
|
||||
<p className="text-gray-600 mb-6 leading-relaxed">
|
||||
Create your account and access our full range of IT solutions and services.
|
||||
</p>
|
||||
|
||||
<div className="mt-auto">
|
||||
<Link
|
||||
href="/auth/signup"
|
||||
className="block border-2 border-blue-600 text-blue-600 px-8 py-4 rounded-xl hover:bg-blue-600 hover:text-white transition-all duration-300 font-semibold text-lg mb-3"
|
||||
>
|
||||
Create Account
|
||||
</Link>
|
||||
<p className="text-sm text-gray-500">Start your journey with us</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Portal Features Section */}
|
||||
<section className="py-16">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl font-bold text-gray-900 mb-4">Portal Features</h2>
|
||||
<p className="text-lg text-gray-600">
|
||||
Everything you need to manage your Assist Solutions services
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<div className="bg-white rounded-lg p-6 text-center shadow-lg hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1">
|
||||
<div className="flex justify-center mb-3">
|
||||
<CreditCardIcon className="w-8 h-8 text-blue-600" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold mb-2 text-gray-900">Billing & Payments</h3>
|
||||
<p className="text-gray-600 text-sm">
|
||||
View invoices, payment history, and manage billing
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-lg p-6 text-center shadow-lg hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1">
|
||||
<div className="flex justify-center mb-3">
|
||||
<Cog6ToothIcon className="w-8 h-8 text-purple-600" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold mb-2 text-gray-900">Service Management</h3>
|
||||
<p className="text-gray-600 text-sm">Control and configure your active services</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-lg p-6 text-center shadow-lg hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1">
|
||||
<div className="flex justify-center mb-3">
|
||||
<PhoneIcon className="w-8 h-8 text-pink-600" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold mb-2 text-gray-900">Support Tickets</h3>
|
||||
<p className="text-gray-600 text-sm">Create and track support requests</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-lg p-6 text-center shadow-lg hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1">
|
||||
<div className="flex justify-center mb-3">
|
||||
<ChartBarIcon className="w-8 h-8 text-blue-600" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold mb-2 text-gray-900">Usage Reports</h3>
|
||||
<p className="text-gray-600 text-sm">Monitor service usage and performance</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Support Section */}
|
||||
<section className="py-16">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl font-bold text-gray-900 mb-4">Need Help?</h2>
|
||||
<p className="text-lg text-gray-600">Our support team is here to assist you</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
{/* Contact Details Box */}
|
||||
<div className="bg-white rounded-lg p-8 shadow-lg hover:shadow-xl transition-all duration-300">
|
||||
<h3 className="text-xl font-semibold mb-6 text-gray-900">Contact Details</h3>
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<div className="text-center">
|
||||
<h4 className="font-semibold text-gray-900 mb-2">Phone Support</h4>
|
||||
<p className="text-gray-600 text-sm mb-1">9:30-18:00 JST</p>
|
||||
<p className="text-blue-600 font-medium">0120-660-470</p>
|
||||
<p className="text-gray-500 text-sm">Toll Free within Japan</p>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<h4 className="font-semibold text-gray-900 mb-2">Email Support</h4>
|
||||
<p className="text-gray-600 text-sm mb-1">Response within 24h</p>
|
||||
<Link
|
||||
href="/contact"
|
||||
className="text-purple-600 font-medium hover:text-purple-700"
|
||||
>
|
||||
Send Message
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Live Chat & Business Hours Box */}
|
||||
<div className="bg-white rounded-lg p-8 shadow-lg hover:shadow-xl transition-all duration-300">
|
||||
<h3 className="text-xl font-semibold mb-6 text-gray-900">
|
||||
Live Chat & Business Hours
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<div className="text-center">
|
||||
<h4 className="font-semibold text-gray-900 mb-2">Live Chat</h4>
|
||||
<p className="text-gray-600 text-sm mb-1">Available 24/7</p>
|
||||
<Link href="/chat" className="text-green-600 font-medium hover:text-green-700">
|
||||
Start Chat
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="font-semibold text-gray-900 mb-3">Business Hours</h4>
|
||||
<div className="text-gray-600 text-sm space-y-1">
|
||||
<p>
|
||||
<strong>Monday - Saturday:</strong>
|
||||
<br />
|
||||
10:00 AM - 6:00 PM JST
|
||||
</p>
|
||||
<p>
|
||||
<strong>Sunday:</strong>
|
||||
<br />
|
||||
Closed
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="bg-white/90 backdrop-blur-sm text-gray-900 py-8 border-t border-gray-200">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center">
|
||||
<p className="text-gray-600 text-sm">
|
||||
© 2025 Assist Solutions Corp. All Rights Reserved.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,2 +1,3 @@
|
||||
export * from "./components";
|
||||
export * from "./hooks";
|
||||
export * from "./views";
|
||||
|
||||
@ -1,23 +1,16 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useAuthStore } from "@/features/auth/services/auth.store";
|
||||
import { useDashboardSummary } from "@/features/dashboard/hooks/useDashboard";
|
||||
|
||||
import type { Activity } from "@customer-portal/domain";
|
||||
import type { Activity, DashboardSummary } from "@customer-portal/domain";
|
||||
import {
|
||||
CreditCardIcon,
|
||||
ServerIcon,
|
||||
ChatBubbleLeftRightIcon,
|
||||
ExclamationTriangleIcon,
|
||||
ChevronRightIcon,
|
||||
PlusIcon,
|
||||
DocumentTextIcon,
|
||||
ArrowTrendingUpIcon,
|
||||
CalendarDaysIcon,
|
||||
BellIcon,
|
||||
ClipboardDocumentListIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import {
|
||||
CreditCardIcon as CreditCardIconSolid,
|
||||
@ -26,15 +19,19 @@ import {
|
||||
ClipboardDocumentListIcon as ClipboardDocumentListIconSolid,
|
||||
} from "@heroicons/react/24/solid";
|
||||
import { format, formatDistanceToNow } from "date-fns";
|
||||
|
||||
import { useAuthStore } from "@/features/auth/services/auth.store";
|
||||
import { useDashboardSummary } from "@/features/dashboard/hooks";
|
||||
import { StatCard, QuickAction, DashboardActivityItem } from "@/features/dashboard/components";
|
||||
import { LoadingSpinner } from "@/components/ui/loading-skeleton";
|
||||
import { ErrorState } from "@/components/ui/error-state";
|
||||
import { formatCurrency, getCurrencyLocale } from "@customer-portal/domain";
|
||||
|
||||
export default function DashboardPage() {
|
||||
export function DashboardView() {
|
||||
const router = useRouter();
|
||||
const { user, isAuthenticated, loading: authLoading } = useAuthStore();
|
||||
const { data: summary, isLoading: summaryLoading, error } = useDashboardSummary();
|
||||
const upcomingInvoice = summary?.nextInvoice ?? null;
|
||||
|
||||
const [paymentLoading, setPaymentLoading] = useState(false);
|
||||
const [paymentError, setPaymentError] = useState<string | null>(null);
|
||||
@ -115,7 +112,7 @@ export default function DashboardPage() {
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-[var(--cp-space-2xl)] mb-[var(--cp-space-3xl)]">
|
||||
<StatCard
|
||||
title="Recent Orders"
|
||||
value={((summary?.stats as Record<string, unknown>)?.recentOrders as number) || 0}
|
||||
value={summary?.stats?.recentOrders ?? 0}
|
||||
icon={ClipboardDocumentListIconSolid}
|
||||
gradient="from-gray-500 to-gray-600"
|
||||
href="/orders"
|
||||
@ -157,7 +154,7 @@ export default function DashboardPage() {
|
||||
{/* Main Content Area */}
|
||||
<div className="lg:col-span-2 space-y-[var(--cp-space-3xl)]">
|
||||
{/* Upcoming Payment - compressed attention banner */}
|
||||
{summary?.nextInvoice && (
|
||||
{upcomingInvoice && (
|
||||
<div
|
||||
id="attention"
|
||||
className="bg-white rounded-xl border border-orange-200 shadow-sm p-4"
|
||||
@ -172,29 +169,28 @@ export default function DashboardPage() {
|
||||
<div className="flex flex-wrap items-center gap-2 text-sm text-gray-700">
|
||||
<span className="font-semibold text-gray-900">Upcoming Payment</span>
|
||||
<span className="text-gray-400">•</span>
|
||||
<span>Invoice #{summary.nextInvoice.id}</span>
|
||||
<span>Invoice #{upcomingInvoice.id}</span>
|
||||
<span className="text-gray-400">•</span>
|
||||
<span title={format(new Date(summary.nextInvoice.dueDate), "MMMM d, yyyy")}>
|
||||
<span title={format(new Date(upcomingInvoice.dueDate), "MMMM d, yyyy")}>
|
||||
Due{" "}
|
||||
{formatDistanceToNow(new Date(summary.nextInvoice.dueDate), {
|
||||
{formatDistanceToNow(new Date(upcomingInvoice.dueDate), {
|
||||
addSuffix: true,
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-1 text-2xl font-bold text-gray-900">
|
||||
{formatCurrency(summary.nextInvoice.amount, {
|
||||
currency: summary.nextInvoice.currency || "JPY",
|
||||
locale: getCurrencyLocale(summary.nextInvoice.currency || "JPY"),
|
||||
{formatCurrency(upcomingInvoice.amount, {
|
||||
currency: upcomingInvoice.currency || "JPY",
|
||||
locale: getCurrencyLocale(upcomingInvoice.currency || "JPY"),
|
||||
})}
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-gray-500">
|
||||
Exact due date:{" "}
|
||||
{format(new Date(summary.nextInvoice.dueDate), "MMMM d, yyyy")}
|
||||
Exact due date: {format(new Date(upcomingInvoice.dueDate), "MMMM d, yyyy")}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-end gap-2">
|
||||
<button
|
||||
onClick={() => handlePayNow(summary.nextInvoice!.id)}
|
||||
onClick={() => handlePayNow(upcomingInvoice.id)}
|
||||
disabled={paymentLoading}
|
||||
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
@ -205,7 +201,7 @@ export default function DashboardPage() {
|
||||
{!paymentLoading && <ChevronRightIcon className="ml-2 h-4 w-4" />}
|
||||
</button>
|
||||
<Link
|
||||
href={`/billing/invoices/${summary.nextInvoice.id}`}
|
||||
href={`/billing/invoices/${upcomingInvoice.id}`}
|
||||
className="text-blue-600 hover:text-blue-700 font-medium text-sm"
|
||||
>
|
||||
View invoice
|
||||
@ -276,12 +272,13 @@ export default function DashboardPage() {
|
||||
}
|
||||
|
||||
// Helpers and small components (local to dashboard)
|
||||
function truncateName(name: string, len = 28) {
|
||||
if (name.length <= len) return name;
|
||||
return name.slice(0, Math.max(0, len - 1)) + "…";
|
||||
}
|
||||
|
||||
function TasksChip({ summaryLoading, summary }: { summaryLoading: boolean; summary: any }) {
|
||||
function TasksChip({
|
||||
summaryLoading,
|
||||
summary,
|
||||
}: {
|
||||
summaryLoading: boolean;
|
||||
summary: DashboardSummary | undefined;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
if (summaryLoading) return null;
|
||||
const tasks: Array<{ label: string; href: string }> = [];
|
||||
@ -339,7 +336,11 @@ function RecentActivityCard({
|
||||
<button
|
||||
key={opt.k}
|
||||
onClick={() => setFilter(opt.k)}
|
||||
className={`px-2.5 py-1 text-xs rounded-md font-medium ${filter === opt.k ? "bg-white text-gray-900 shadow" : "text-gray-600 hover:text-gray-900"}`}
|
||||
className={`px-2.5 py-1 text-xs rounded-md font-medium ${
|
||||
filter === opt.k
|
||||
? "bg-white text-gray-900 shadow"
|
||||
: "text-gray-600 hover:text-gray-900"
|
||||
}`}
|
||||
>
|
||||
{opt.label}
|
||||
</button>
|
||||
1
apps/portal/src/features/dashboard/views/index.ts
Normal file
1
apps/portal/src/features/dashboard/views/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./DashboardView";
|
||||
@ -1,3 +1,5 @@
|
||||
export * as billing from "./billing";
|
||||
export * as subscriptions from "./subscriptions";
|
||||
export * as dashboard from "./dashboard";
|
||||
export * as marketing from "./marketing";
|
||||
export * as support from "./support";
|
||||
|
||||
2
apps/portal/src/features/marketing/index.ts
Normal file
2
apps/portal/src/features/marketing/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./views/PublicLandingView";
|
||||
export * from "./views/PublicLandingLoadingView";
|
||||
@ -1,6 +1,6 @@
|
||||
import { Skeleton } from "@/components/ui/loading-skeleton";
|
||||
|
||||
export default function RootLoading() {
|
||||
export function PublicLandingLoadingView() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-blue-100 to-blue-900">
|
||||
<header className="bg-white/90 backdrop-blur-sm shadow-sm border-b border-blue-100">
|
||||
@ -22,8 +22,11 @@ export default function RootLoading() {
|
||||
<div className="max-w-[var(--cp-page-max-width)] mx-auto px-[var(--cp-page-padding)] sm:px-6 lg:px-8 space-y-8">
|
||||
<Skeleton className="h-12 w-2/3" />
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{Array.from({ length: 3 }).map((_, i) => (
|
||||
<div key={i} className="bg-white rounded-2xl p-8 shadow-lg border border-gray-100 space-y-4">
|
||||
{Array.from({ length: 3 }).map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="bg-white rounded-2xl p-8 shadow-lg border border-gray-100 space-y-4"
|
||||
>
|
||||
<Skeleton className="h-16 w-16 rounded-full mx-auto" />
|
||||
<Skeleton className="h-6 w-1/2 mx-auto" />
|
||||
<Skeleton className="h-4 w-3/4 mx-auto" />
|
||||
@ -36,5 +39,3 @@ export default function RootLoading() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
368
apps/portal/src/features/marketing/views/PublicLandingView.tsx
Normal file
368
apps/portal/src/features/marketing/views/PublicLandingView.tsx
Normal file
@ -0,0 +1,368 @@
|
||||
import Link from "next/link";
|
||||
import { Logo } from "@/components/ui/logo";
|
||||
import {
|
||||
ArrowPathIcon,
|
||||
UserIcon,
|
||||
SparklesIcon,
|
||||
CreditCardIcon,
|
||||
Cog6ToothIcon,
|
||||
PhoneIcon,
|
||||
ChartBarIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
|
||||
export function PublicLandingView() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-blue-100 to-blue-900">
|
||||
{/* Header */}
|
||||
<header className="bg-white/90 backdrop-blur-sm shadow-sm border-b border-blue-100">
|
||||
<div className="max-w-[var(--cp-page-max-width)] mx-auto px-[var(--cp-page-padding)] sm:px-6 lg:px-8">
|
||||
<div className="flex justify-between items-center h-16">
|
||||
<div className="flex items-center space-x-3">
|
||||
<Logo size={40} />
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold text-gray-900">Assist Solutions</h1>
|
||||
<p className="text-xs text-gray-500">Customer Portal</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-3">
|
||||
<Link
|
||||
href="/auth/login"
|
||||
className="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700 transition-colors duration-200 font-medium text-sm"
|
||||
>
|
||||
Login
|
||||
</Link>
|
||||
<Link
|
||||
href="/support"
|
||||
className="text-gray-600 hover:text-gray-900 px-4 py-2 text-sm transition-colors duration-200"
|
||||
>
|
||||
Support
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Hero Section */}
|
||||
<section className="relative py-20 overflow-hidden">
|
||||
{/* Abstract background elements */}
|
||||
<div className="absolute inset-0">
|
||||
<div className="absolute top-20 left-10 w-72 h-72 bg-blue-300 rounded-full mix-blend-multiply filter blur-xl opacity-20 animate-pulse"></div>
|
||||
<div
|
||||
className="absolute top-40 right-10 w-72 h-72 bg-indigo-300 rounded-full mix-blend-multiply filter blur-xl opacity-20 animate-pulse"
|
||||
style={{ animationDelay: "2s" }}
|
||||
></div>
|
||||
<div
|
||||
className="absolute -bottom-8 left-20 w-72 h-72 bg-purple-300 rounded-full mix-blend-multiply filter blur-xl opacity-20 animate-pulse"
|
||||
style={{ animationDelay: "4s" }}
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div className="relative max-w-[var(--cp-page-max-width)] mx-auto px-[var(--cp-page-padding)] sm:px-6 lg:px-8">
|
||||
<div className="text-center">
|
||||
<h1 className="text-5xl md:text-6xl font-bold text-gray-900 mb-[var(--cp-space-2xl)]">
|
||||
New Assist Solutions Customer Portal
|
||||
</h1>
|
||||
|
||||
<div className="flex justify-center">
|
||||
<a
|
||||
href="#portal-access"
|
||||
className="bg-blue-600 text-white px-10 py-4 rounded-xl hover:bg-blue-700 transition-all duration-300 font-semibold text-lg shadow-lg hover:shadow-xl transform hover:-translate-y-1"
|
||||
>
|
||||
Get Started
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Customer Portal Access Section */}
|
||||
<section id="portal-access" className="py-20">
|
||||
<div className="max-w-[var(--cp-page-max-width)] mx-auto px-[var(--cp-page-padding)] sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-3xl font-bold text-gray-900 mb-4">Access Your Portal</h2>
|
||||
<p className="text-lg text-gray-600">Choose the option that applies to you</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-[var(--cp-space-3xl)] items-stretch">
|
||||
{/* Existing Customers - Migration */}
|
||||
<div className="bg-white rounded-[var(--cp-card-radius-lg)] p-[var(--cp-space-3xl)] shadow-[var(--cp-card-shadow)] hover:shadow-[var(--cp-card-shadow-lg)] transition-all duration-[var(--cp-transition-slow)] transform hover:-translate-y-2 border-[var(--cp-card-border)]">
|
||||
<div className="text-center flex-1 flex flex-col">
|
||||
<div className="flex items-center justify-center w-16 h-16 bg-blue-600 rounded-full mb-6 mx-auto">
|
||||
<ArrowPathIcon className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-4">Existing Customers</h3>
|
||||
<p className="text-gray-600 mb-6 leading-relaxed">
|
||||
Migrate to our new portal and enjoy enhanced security with modern interface.
|
||||
</p>
|
||||
|
||||
<div className="mt-auto">
|
||||
<Link
|
||||
href="/auth/link-whmcs"
|
||||
className="block bg-blue-600 text-white px-8 py-4 rounded-xl hover:bg-blue-700 transition-all duration-300 font-semibold text-lg mb-3 shadow-md hover:shadow-lg"
|
||||
>
|
||||
Migrate Your Account
|
||||
</Link>
|
||||
<p className="text-sm text-gray-500">Takes just a few minutes</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Portal Users */}
|
||||
<div className="bg-white rounded-2xl p-8 shadow-lg hover:shadow-xl transition-all duration-300 transform hover:-translate-y-2 border border-gray-100">
|
||||
<div className="text-center flex-1 flex flex-col">
|
||||
<div className="flex items-center justify-center w-16 h-16 bg-blue-600 rounded-full mb-6 mx-auto">
|
||||
<UserIcon className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-4">Portal Users</h3>
|
||||
<p className="text-gray-600 mb-6 leading-relaxed">
|
||||
Sign in to access your dashboard and manage all your services efficiently.
|
||||
</p>
|
||||
|
||||
<div className="mt-auto">
|
||||
<Link
|
||||
href="/auth/login"
|
||||
className="block border-2 border-blue-600 text-blue-600 px-8 py-4 rounded-xl hover:bg-blue-600 hover:text-white transition-all duration-300 font-semibold text-lg mb-3"
|
||||
>
|
||||
Login to Portal
|
||||
</Link>
|
||||
<p className="text-sm text-gray-500">Secure access to your account</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* New Customers */}
|
||||
<div className="bg-white rounded-2xl p-8 shadow-lg hover:shadow-xl transition-all duration-300 transform hover:-translate-y-2 border border-gray-100">
|
||||
<div className="text-center flex-1 flex flex-col">
|
||||
<div className="flex items-center justify-center w-16 h-16 bg-blue-600 rounded-full mb-6 mx-auto">
|
||||
<SparklesIcon className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-4">New Customers</h3>
|
||||
<p className="text-gray-600 mb-6 leading-relaxed">
|
||||
Create your account and access our full range of IT solutions and services.
|
||||
</p>
|
||||
|
||||
<div className="mt-auto">
|
||||
<Link
|
||||
href="/auth/signup"
|
||||
className="block border-2 border-blue-600 text-blue-600 px-8 py-4 rounded-xl hover:bg-blue-600 hover:text-white transition-all duration-300 font-semibold text-lg mb-3"
|
||||
>
|
||||
Create Account
|
||||
</Link>
|
||||
<p className="text-sm text-gray-500">Start your journey with us</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Portal Features Section */}
|
||||
<section className="bg-white py-20">
|
||||
<div className="max-w-[var(--cp-page-max-width)] mx-auto px-[var(--cp-page-padding)] sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-3xl font-bold text-gray-900 mb-4">Why Choose Assist Solutions</h2>
|
||||
<p className="text-lg text-gray-600">
|
||||
Modern tools to manage your IT services with confidence
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-[var(--cp-space-3xl)]">
|
||||
<div className="bg-white border border-gray-100 rounded-[var(--cp-card-radius-lg)] p-[var(--cp-space-3xl)] shadow-[var(--cp-card-shadow)] hover:shadow-[var(--cp-card-shadow-lg)] transition-shadow duration-300">
|
||||
<div className="flex items-center justify-center w-14 h-14 bg-blue-100 rounded-full mb-6">
|
||||
<CreditCardIcon className="w-7 h-7 text-blue-600" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-3">Automated Billing</h3>
|
||||
<p className="text-gray-600">
|
||||
Transparent invoicing, automated payments, and flexible billing options tailored to
|
||||
your business.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white border border-gray-100 rounded-[var(--cp-card-radius-lg)] p-[var(--cp-space-3xl)] shadow-[var(--cp-card-shadow)] hover:shadow-[var(--cp-card-shadow-lg)] transition-shadow duration-300">
|
||||
<div className="flex items-center justify-center w-14 h-14 bg-blue-100 rounded-full mb-6">
|
||||
<Cog6ToothIcon className="w-7 h-7 text-blue-600" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-3">Service Management</h3>
|
||||
<p className="text-gray-600">
|
||||
Control subscriptions, manage network services, and track usage from a single pane
|
||||
of glass.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white border border-gray-100 rounded-[var(--cp-card-radius-lg)] p-[var(--cp-space-3xl)] shadow-[var(--cp-card-shadow)] hover:shadow-[var(--cp-card-shadow-lg)] transition-shadow duration-300">
|
||||
<div className="flex items-center justify-center w-14 h-14 bg-blue-100 rounded-full mb-6">
|
||||
<PhoneIcon className="w-7 h-7 text-blue-600" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-3">Expert Support</h3>
|
||||
<p className="text-gray-600">
|
||||
Dedicated support team with SLA-backed response times and proactive service
|
||||
monitoring.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white border border-gray-100 rounded-[var(--cp-card-radius-lg)] p-[var(--cp-space-3xl)] shadow-[var(--cp-card-shadow)] hover:shadow-[var(--cp-card-shadow-lg)] transition-shadow duration-300">
|
||||
<div className="flex items-center justify-center w-14 h-14 bg-blue-100 rounded-full mb-6">
|
||||
<ChartBarIcon className="w-7 h-7 text-blue-600" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-3">Actionable Insights</h3>
|
||||
<p className="text-gray-600">
|
||||
Real-time analytics and reporting to help you optimize resource usage and forecast
|
||||
demand.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white border border-gray-100 rounded-[var(--cp-card-radius-lg)] p-[var(--cp-space-3xl)] shadow-[var(--cp-card-shadow)] hover:shadow-[var(--cp-card-shadow-lg)] transition-shadow duration-300">
|
||||
<div className="flex items-center justify-center w-14 h-14 bg-blue-100 rounded-full mb-6">
|
||||
<ArrowPathIcon className="w-7 h-7 text-blue-600" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-3">Seamless Migration</h3>
|
||||
<p className="text-gray-600">
|
||||
Guided onboarding for WHMCS customers with automatic data migration and validation.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white border border-gray-100 rounded-[var(--cp-card-radius-lg)] p-[var(--cp-space-3xl)] shadow-[var(--cp-card-shadow)] hover:shadow-[var(--cp-card-shadow-lg)] transition-shadow duration-300">
|
||||
<div className="flex items-center justify-center w-14 h-14 bg-blue-100 rounded-full mb-6">
|
||||
<SparklesIcon className="w-7 h-7 text-blue-600" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-3">Future-Proof Platform</h3>
|
||||
<p className="text-gray-600">
|
||||
Built on modern infrastructure with continuous updates, security patches, and new
|
||||
features.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className="bg-gradient-to-br from-blue-900 via-blue-800 to-blue-700 py-20">
|
||||
<div className="max-w-[var(--cp-page-max-width)] mx-auto px-[var(--cp-page-padding)] sm:px-6 lg:px-8">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-center">
|
||||
<div>
|
||||
<h2 className="text-4xl font-bold text-white mb-6">
|
||||
Ready to experience the new portal?
|
||||
</h2>
|
||||
<p className="text-blue-100 text-lg mb-8">
|
||||
Join thousands of customers who trust Assist Solutions to keep their business
|
||||
connected and secure.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4">
|
||||
<Link
|
||||
href="/auth/signup"
|
||||
className="inline-flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-lg text-blue-600 bg-white hover:bg-blue-50 transition-colors duration-200"
|
||||
>
|
||||
Create an Account
|
||||
</Link>
|
||||
<Link
|
||||
href="/auth/login"
|
||||
className="inline-flex items-center justify-center px-8 py-3 border border-blue-300 text-base font-medium rounded-lg text-white bg-transparent hover:bg-blue-800 transition-colors duration-200"
|
||||
>
|
||||
Portal Login
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white/10 backdrop-blur rounded-2xl p-8 border border-white/20">
|
||||
<h3 className="text-xl font-semibold text-white mb-4">What’s included?</h3>
|
||||
<ul className="space-y-4 text-blue-100">
|
||||
<li className="flex items-start gap-3">
|
||||
<span className="mt-1 h-2 w-2 rounded-full bg-blue-300"></span>
|
||||
<span>Centralized service and subscription management dashboard</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<span className="mt-1 h-2 w-2 rounded-full bg-blue-300"></span>
|
||||
<span>Automated billing with support for multiple payment methods</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<span className="mt-1 h-2 w-2 rounded-full bg-blue-300"></span>
|
||||
<span>Priority access to our customer support specialists</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<span className="mt-1 h-2 w-2 rounded-full bg-blue-300"></span>
|
||||
<span>Insights and analytics to track usage and growth</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="bg-blue-950 text-blue-100 py-10">
|
||||
<div className="max-w-[var(--cp-page-max-width)] mx-auto px-[var(--cp-page-padding)] sm:px-6 lg:px-8">
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-8">
|
||||
<div>
|
||||
<Logo size={32} />
|
||||
<p className="mt-4 text-sm text-blue-200">
|
||||
Delivering reliable IT solutions and support for businesses of all sizes.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-white uppercase tracking-wider">Portal</h3>
|
||||
<ul className="mt-4 space-y-2 text-sm">
|
||||
<li>
|
||||
<Link
|
||||
href="/auth/login"
|
||||
className="hover:text-white transition-colors duration-200"
|
||||
>
|
||||
Login
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/auth/signup"
|
||||
className="hover:text-white transition-colors duration-200"
|
||||
>
|
||||
Create Account
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/support" className="hover:text-white transition-colors duration-200">
|
||||
Support Center
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-white uppercase tracking-wider">
|
||||
Solutions
|
||||
</h3>
|
||||
<ul className="mt-4 space-y-2 text-sm">
|
||||
<li>Network Services</li>
|
||||
<li>Managed Security</li>
|
||||
<li>Cloud Infrastructure</li>
|
||||
<li>Professional Services</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-white uppercase tracking-wider">Contact</h3>
|
||||
<ul className="mt-4 space-y-2 text-sm">
|
||||
<li>support@assistsolutions.com</li>
|
||||
<li>+1 (800) 555-0123</li>
|
||||
<li>123 Innovation Drive, Suite 400</li>
|
||||
<li>San Francisco, CA 94105</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-10 border-t border-blue-800 pt-6 text-sm text-blue-300 flex flex-col sm:flex-row justify-between gap-4">
|
||||
<p>© {new Date().getFullYear()} Assist Solutions. All rights reserved.</p>
|
||||
<div className="flex gap-6">
|
||||
<Link href="#" className="hover:text-white transition-colors duration-200">
|
||||
Privacy Policy
|
||||
</Link>
|
||||
<Link href="#" className="hover:text-white transition-colors duration-200">
|
||||
Terms of Service
|
||||
</Link>
|
||||
<Link href="#" className="hover:text-white transition-colors duration-200">
|
||||
Status
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,9 +1 @@
|
||||
/**
|
||||
* Support Feature Module
|
||||
* Customer support functionality including components, hooks, and services
|
||||
*
|
||||
* Note: This feature module is currently empty and ready for future implementation
|
||||
*/
|
||||
|
||||
// This feature module is not yet implemented
|
||||
// Components, hooks, services, types, and utilities will be added as needed
|
||||
export * from "./views";
|
||||
|
||||
247
apps/portal/src/features/support/views/NewSupportCaseView.tsx
Normal file
247
apps/portal/src/features/support/views/NewSupportCaseView.tsx
Normal file
@ -0,0 +1,247 @@
|
||||
"use client";
|
||||
|
||||
import { useState, type FormEvent } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import {
|
||||
ArrowLeftIcon,
|
||||
PaperAirplaneIcon,
|
||||
ExclamationCircleIcon,
|
||||
InformationCircleIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
|
||||
import { logger } from "@customer-portal/logging";
|
||||
|
||||
export function NewSupportCaseView() {
|
||||
const router = useRouter();
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [formData, setFormData] = useState({
|
||||
subject: "",
|
||||
category: "Technical",
|
||||
priority: "Medium",
|
||||
description: "",
|
||||
});
|
||||
|
||||
const handleSubmit = (event: FormEvent) => {
|
||||
event.preventDefault();
|
||||
setIsSubmitting(true);
|
||||
|
||||
// Mock submission - would normally send to API
|
||||
void (async () => {
|
||||
try {
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
// Redirect to cases list with success message
|
||||
router.push("/support/cases?created=true");
|
||||
} catch (error) {
|
||||
logger.error({ error }, "Error creating case");
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
const handleInputChange = (field: string, value: string) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
[field]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
const isFormValid = formData.subject.trim() && formData.description.trim();
|
||||
|
||||
return (
|
||||
<div className="py-6">
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 md:px-8">
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center space-x-4 mb-4">
|
||||
<button
|
||||
onClick={() => router.back()}
|
||||
className="inline-flex items-center text-sm text-gray-500 hover:text-gray-700"
|
||||
>
|
||||
<ArrowLeftIcon className="h-4 w-4 mr-1" />
|
||||
Back to Support
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900">Create Support Case</h1>
|
||||
<p className="mt-1 text-sm text-gray-600">Get help from our support team</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Help Tips */}
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
<InformationCircleIcon className="h-5 w-5 text-blue-400" />
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<h3 className="text-sm font-medium text-blue-800">Before creating a case</h3>
|
||||
<div className="mt-2 text-sm text-blue-700">
|
||||
<ul className="list-disc list-inside space-y-1">
|
||||
<li>Check our knowledge base for common solutions</li>
|
||||
<li>Include relevant error messages or screenshots</li>
|
||||
<li>Provide detailed steps to reproduce the issue</li>
|
||||
<li>Mention your service or subscription if applicable</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Form */}
|
||||
<div className="bg-white shadow rounded-lg">
|
||||
<form onSubmit={handleSubmit} className="p-6 space-y-6">
|
||||
{/* Subject */}
|
||||
<div>
|
||||
<label htmlFor="subject" className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Subject *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="subject"
|
||||
value={formData.subject}
|
||||
onChange={event => handleInputChange("subject", event.target.value)}
|
||||
placeholder="Brief description of your issue"
|
||||
className="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Category and Priority */}
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
||||
<div>
|
||||
<label htmlFor="category" className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Category
|
||||
</label>
|
||||
<select
|
||||
id="category"
|
||||
value={formData.category}
|
||||
onChange={event => handleInputChange("category", event.target.value)}
|
||||
className="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
|
||||
>
|
||||
<option value="Technical">Technical Support</option>
|
||||
<option value="Billing">Billing Question</option>
|
||||
<option value="General">General Inquiry</option>
|
||||
<option value="Feature Request">Feature Request</option>
|
||||
<option value="Bug Report">Bug Report</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="priority" className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Priority
|
||||
</label>
|
||||
<select
|
||||
id="priority"
|
||||
value={formData.priority}
|
||||
onChange={event => handleInputChange("priority", event.target.value)}
|
||||
className="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
|
||||
>
|
||||
<option value="Low">Low - General question</option>
|
||||
<option value="Medium">Medium - Issue affecting work</option>
|
||||
<option value="High">High - Service disruption</option>
|
||||
<option value="Critical">Critical - Complete outage</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<div>
|
||||
<label htmlFor="description" className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Description *
|
||||
</label>
|
||||
<textarea
|
||||
id="description"
|
||||
rows={6}
|
||||
value={formData.description}
|
||||
onChange={event => handleInputChange("description", event.target.value)}
|
||||
placeholder="Please provide a detailed description of your issue, including any error messages and steps to reproduce the problem..."
|
||||
className="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500"
|
||||
required
|
||||
/>
|
||||
<p className="mt-2 text-xs text-gray-500">
|
||||
The more details you provide, the faster we can help you.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Priority Warning */}
|
||||
{formData.priority === "Critical" && (
|
||||
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
<ExclamationCircleIcon className="h-5 w-5 text-red-400" />
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<h3 className="text-sm font-medium text-red-800">Critical Priority Selected</h3>
|
||||
<div className="mt-2 text-sm text-red-700">
|
||||
<p>
|
||||
Critical priority should only be used for complete service outages. For
|
||||
urgent issues that aren't complete outages, please use High priority.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center justify-between pt-6 border-t border-gray-200">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.back()}
|
||||
className="inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!isFormValid || isSubmitting}
|
||||
className="inline-flex items-center px-6 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 disabled:bg-gray-300 disabled:cursor-not-allowed"
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
|
||||
Creating Case...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<PaperAirplaneIcon className="h-4 w-4 mr-2" />
|
||||
Create Case
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{/* Additional Help */}
|
||||
<div className="mt-8 bg-gray-50 rounded-lg p-6">
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-4">Need immediate help?</h3>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-gray-900">Phone Support</h4>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
9:30-18:00 JST
|
||||
<br />
|
||||
<span className="font-medium text-blue-600">0120-660-470</span>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-gray-900">Knowledge Base</h4>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
Search our help articles for quick solutions
|
||||
<br />
|
||||
<Link href="/support/kb" className="font-medium text-blue-600 hover:text-blue-500">
|
||||
Browse Knowledge Base
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
ChatBubbleLeftRightIcon,
|
||||
@ -28,7 +28,7 @@ interface SupportCase {
|
||||
assignedTo?: string;
|
||||
}
|
||||
|
||||
export default function SupportCasesPage() {
|
||||
export function SupportCasesView() {
|
||||
const [cases, setCases] = useState<SupportCase[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
@ -99,10 +99,12 @@ export default function SupportCasesPage() {
|
||||
},
|
||||
];
|
||||
|
||||
setTimeout(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
setCases(mockCases);
|
||||
setLoading(false);
|
||||
}, 500);
|
||||
|
||||
return () => clearTimeout(timeout);
|
||||
}, []);
|
||||
|
||||
// Filter cases based on search, status, and priority
|
||||
@ -228,8 +230,8 @@ export default function SupportCasesPage() {
|
||||
<dt className="text-sm font-medium text-gray-500 truncate">Open Cases</dt>
|
||||
<dd className="text-lg font-medium text-gray-900">
|
||||
{
|
||||
cases.filter(c =>
|
||||
["Open", "In Progress", "Waiting on Customer"].includes(c.status)
|
||||
cases.filter(caseItem =>
|
||||
["Open", "In Progress", "Waiting on Customer"].includes(caseItem.status)
|
||||
).length
|
||||
}
|
||||
</dd>
|
||||
@ -249,7 +251,10 @@ export default function SupportCasesPage() {
|
||||
<dl>
|
||||
<dt className="text-sm font-medium text-gray-500 truncate">High Priority</dt>
|
||||
<dd className="text-lg font-medium text-gray-900">
|
||||
{cases.filter(c => ["High", "Critical"].includes(c.priority)).length}
|
||||
{
|
||||
cases.filter(caseItem => ["High", "Critical"].includes(caseItem.priority))
|
||||
.length
|
||||
}
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
@ -267,7 +272,10 @@ export default function SupportCasesPage() {
|
||||
<dl>
|
||||
<dt className="text-sm font-medium text-gray-500 truncate">Resolved</dt>
|
||||
<dd className="text-lg font-medium text-gray-900">
|
||||
{cases.filter(c => ["Resolved", "Closed"].includes(c.status)).length}
|
||||
{
|
||||
cases.filter(caseItem => ["Resolved", "Closed"].includes(caseItem.status))
|
||||
.length
|
||||
}
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
@ -289,7 +297,7 @@ export default function SupportCasesPage() {
|
||||
type="text"
|
||||
placeholder="Search cases..."
|
||||
value={searchTerm}
|
||||
onChange={e => setSearchTerm(e.target.value)}
|
||||
onChange={event => setSearchTerm(event.target.value)}
|
||||
className="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md leading-5 bg-white placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
@ -298,7 +306,7 @@ export default function SupportCasesPage() {
|
||||
<div className="relative">
|
||||
<select
|
||||
value={statusFilter}
|
||||
onChange={e => setStatusFilter(e.target.value)}
|
||||
onChange={event => setStatusFilter(event.target.value)}
|
||||
className="block w-full px-3 py-2 border border-gray-300 rounded-md leading-5 bg-white focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
|
||||
>
|
||||
<option value="all">All Statuses</option>
|
||||
@ -314,7 +322,7 @@ export default function SupportCasesPage() {
|
||||
<div className="relative">
|
||||
<select
|
||||
value={priorityFilter}
|
||||
onChange={e => setPriorityFilter(e.target.value)}
|
||||
onChange={event => setPriorityFilter(event.target.value)}
|
||||
className="block w-full px-3 py-2 border border-gray-300 rounded-md leading-5 bg-white focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
|
||||
>
|
||||
<option value="all">All Priorities</option>
|
||||
2
apps/portal/src/features/support/views/index.ts
Normal file
2
apps/portal/src/features/support/views/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./NewSupportCaseView";
|
||||
export * from "./SupportCasesView";
|
||||
@ -15,6 +15,12 @@
|
||||
// Path mappings
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"@/components/*": ["./src/components/*"],
|
||||
"@/core/*": ["./src/core/*"],
|
||||
"@/features/*": ["./src/features/*"],
|
||||
"@/shared/*": ["./src/shared/*"],
|
||||
"@/styles/*": ["./src/styles/*"],
|
||||
"@/types/*": ["./src/types/*"],
|
||||
},
|
||||
// Enforce TS-only in portal and keep strict mode explicit (inherits from root)
|
||||
"allowJs": false,
|
||||
|
||||
@ -30,7 +30,7 @@ apps/portal/src/features/service-management/
|
||||
|
||||
## Integration
|
||||
|
||||
- Entry point: `apps/portal/src/app/(portal)/subscriptions/[id]/page.tsx` renders `ServiceManagementSection`
|
||||
- Entry point: `apps/portal/src/app/(authenticated)/subscriptions/[id]/page.tsx` renders `ServiceManagementSection`
|
||||
- Detection: SIM availability is inferred from `subscription.productName` including `sim` (case-insensitive)
|
||||
|
||||
## Future Expansion
|
||||
|
||||
@ -99,10 +99,10 @@ export default [
|
||||
},
|
||||
},
|
||||
|
||||
// Prevent importing the DashboardLayout directly in (portal) pages.
|
||||
// Pages should rely on the shared route-group layout at (portal)/layout.tsx.
|
||||
// Prevent importing the DashboardLayout directly in (authenticated) pages.
|
||||
// Pages should rely on the shared route-group layout at (authenticated)/layout.tsx.
|
||||
{
|
||||
files: ["apps/portal/src/app/(portal)/**/*.{ts,tsx}"],
|
||||
files: ["apps/portal/src/app/(authenticated)/**/*.{ts,tsx}"],
|
||||
rules: {
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
@ -111,7 +111,7 @@ export default [
|
||||
{
|
||||
group: ["@/components/layout/dashboard-layout"],
|
||||
message:
|
||||
"Use the shared (portal)/layout.tsx instead of importing DashboardLayout in pages.",
|
||||
"Use the shared (authenticated)/layout.tsx instead of importing DashboardLayout in pages.",
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -133,14 +133,14 @@ export default [
|
||||
},
|
||||
// Allow the shared layout file itself to import the layout component
|
||||
{
|
||||
files: ["apps/portal/src/app/(portal)/layout.tsx"],
|
||||
files: ["apps/portal/src/app/(authenticated)/layout.tsx"],
|
||||
rules: {
|
||||
"no-restricted-imports": "off",
|
||||
},
|
||||
},
|
||||
// Allow controlled window.location usage for invoice SSO download
|
||||
{
|
||||
files: ["apps/portal/src/app/(portal)/billing/invoices/[id]/page.tsx"],
|
||||
files: ["apps/portal/src/app/(authenticated)/billing/invoices/[id]/page.tsx"],
|
||||
rules: {
|
||||
"no-restricted-syntax": "off",
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user