Phase 5: Animations & Final Polish
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
import { motion } from 'framer-motion'
|
||||
import { Button, CheckCircleIcon } from '@ciphera-net/ui'
|
||||
import { useAuth } from '@/lib/auth/context'
|
||||
import { initiateOAuthFlow } from '@/lib/api/oauth'
|
||||
@@ -212,17 +213,27 @@ export default function PricingSection() {
|
||||
|
||||
return (
|
||||
<section className="py-24 px-4 max-w-6xl mx-auto">
|
||||
<div className="text-center mb-12">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<h2 className="text-4xl md:text-5xl font-bold text-neutral-900 dark:text-white mb-6 tracking-tight">
|
||||
Transparent Pricing
|
||||
</h2>
|
||||
<p className="text-xl text-neutral-600 dark:text-neutral-400">
|
||||
Scale with your traffic. No hidden fees.
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Unified Container */}
|
||||
<div className="max-w-6xl mx-auto border border-neutral-200 dark:border-neutral-800 rounded-3xl bg-white/50 dark:bg-neutral-900/50 backdrop-blur-xl shadow-sm overflow-hidden mb-20">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5, delay: 0.1 }}
|
||||
className="max-w-6xl mx-auto border border-neutral-200 dark:border-neutral-800 rounded-3xl bg-white/50 dark:bg-neutral-900/50 backdrop-blur-xl shadow-sm overflow-hidden mb-20"
|
||||
>
|
||||
|
||||
{/* Top Toolbar */}
|
||||
<div className="p-8 border-b border-neutral-200 dark:border-neutral-800 flex flex-col md:flex-row items-center justify-between gap-8 bg-neutral-50/50 dark:bg-neutral-900/50">
|
||||
@@ -380,7 +391,7 @@ export default function PricingSection() {
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -92,8 +92,9 @@ export default function Campaigns({ siteId, dateRange }: CampaignsProps) {
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
<div className="space-y-2 flex-1 min-h-[270px] flex flex-col items-center justify-center">
|
||||
<p className="text-neutral-500">Loading...</p>
|
||||
<div className="space-y-2 flex-1 min-h-[270px] flex flex-col items-center justify-center gap-2">
|
||||
<div className="animate-spin w-6 h-6 border-2 border-neutral-300 dark:border-neutral-700 border-t-brand-orange rounded-full" />
|
||||
<p className="text-sm text-neutral-500 dark:text-neutral-400">Loading...</p>
|
||||
</div>
|
||||
) : hasData ? (
|
||||
<div className="space-y-2 flex-1 min-h-[270px]">
|
||||
@@ -152,7 +153,10 @@ export default function Campaigns({ siteId, dateRange }: CampaignsProps) {
|
||||
>
|
||||
<div className="space-y-3 max-h-[60vh] overflow-y-auto pr-2">
|
||||
{isLoadingFull ? (
|
||||
<div className="py-4 text-center text-neutral-500">Loading...</div>
|
||||
<div className="py-8 flex flex-col items-center gap-2">
|
||||
<div className="animate-spin w-6 h-6 border-2 border-neutral-300 dark:border-neutral-700 border-t-brand-orange rounded-full" />
|
||||
<p className="text-sm text-neutral-500 dark:text-neutral-400">Loading...</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="grid grid-cols-12 gap-2 text-xs font-medium text-neutral-500 dark:text-neutral-400 mb-2 px-2 sticky top-0 bg-white dark:bg-neutral-900 py-2 z-10">
|
||||
|
||||
@@ -171,7 +171,10 @@ export default function ContentStats({ topPages, entryPages, exitPages, domain,
|
||||
>
|
||||
<div className="space-y-3 max-h-[60vh] overflow-y-auto pr-2">
|
||||
{isLoadingFull ? (
|
||||
<div className="py-4 text-center text-neutral-500">Loading...</div>
|
||||
<div className="py-8 flex flex-col items-center gap-2">
|
||||
<div className="animate-spin w-6 h-6 border-2 border-neutral-300 dark:border-neutral-700 border-t-brand-orange rounded-full" />
|
||||
<p className="text-sm text-neutral-500 dark:text-neutral-400">Loading...</p>
|
||||
</div>
|
||||
) : (
|
||||
(fullData.length > 0 ? fullData : data).map((page, index) => (
|
||||
<div key={index} className="flex items-center justify-between py-2 group hover:bg-neutral-50 dark:hover:bg-neutral-800 rounded-lg px-2 -mx-2 transition-colors">
|
||||
|
||||
@@ -286,7 +286,10 @@ export default function Locations({ countries, cities, regions, geoDataLevel = '
|
||||
>
|
||||
<div className="space-y-3 max-h-[60vh] overflow-y-auto pr-2">
|
||||
{isLoadingFull ? (
|
||||
<div className="py-4 text-center text-neutral-500">Loading...</div>
|
||||
<div className="py-8 flex flex-col items-center gap-2">
|
||||
<div className="animate-spin w-6 h-6 border-2 border-neutral-300 dark:border-neutral-700 border-t-brand-orange rounded-full" />
|
||||
<p className="text-sm text-neutral-500 dark:text-neutral-400">Loading...</p>
|
||||
</div>
|
||||
) : (
|
||||
(fullData.length > 0 ? fullData : data as any[]).map((item, index) => (
|
||||
<div key={index} className="flex items-center justify-between py-2 group hover:bg-neutral-50 dark:hover:bg-neutral-800 rounded-lg px-2 -mx-2 transition-colors">
|
||||
|
||||
@@ -187,7 +187,10 @@ export default function TechSpecs({ browsers, os, devices, screenResolutions, co
|
||||
>
|
||||
<div className="space-y-3 max-h-[60vh] overflow-y-auto pr-2">
|
||||
{isLoadingFull ? (
|
||||
<div className="py-4 text-center text-neutral-500">Loading...</div>
|
||||
<div className="py-8 flex flex-col items-center gap-2">
|
||||
<div className="animate-spin w-6 h-6 border-2 border-neutral-300 dark:border-neutral-700 border-t-brand-orange rounded-full" />
|
||||
<p className="text-sm text-neutral-500 dark:text-neutral-400">Loading...</p>
|
||||
</div>
|
||||
) : (
|
||||
(fullData.length > 0 ? fullData : data).map((item, index) => (
|
||||
<div key={index} className="flex items-center justify-between py-2 group hover:bg-neutral-50 dark:hover:bg-neutral-800 rounded-lg px-2 -mx-2 transition-colors">
|
||||
|
||||
@@ -115,7 +115,10 @@ export default function TopReferrers({ referrers, collectReferrers = true, siteI
|
||||
>
|
||||
<div className="space-y-3 max-h-[60vh] overflow-y-auto pr-2">
|
||||
{isLoadingFull ? (
|
||||
<div className="py-4 text-center text-neutral-500">Loading...</div>
|
||||
<div className="py-8 flex flex-col items-center gap-2">
|
||||
<div className="animate-spin w-6 h-6 border-2 border-neutral-300 dark:border-neutral-700 border-t-brand-orange rounded-full" />
|
||||
<p className="text-sm text-neutral-500 dark:text-neutral-400">Loading...</p>
|
||||
</div>
|
||||
) : (
|
||||
(fullData.length > 0 ? fullData : filteredReferrers).map((ref, index) => (
|
||||
<div key={index} className="flex items-center justify-between py-2 group hover:bg-neutral-50 dark:hover:bg-neutral-800 rounded-lg px-2 -mx-2 transition-colors">
|
||||
|
||||
Reference in New Issue
Block a user