feat: Apple Watch app + Paywall + Privacy Policy + rebranding

## Major Features
- Apple Watch companion app (6 phases complete)
  - WatchConnectivity iPhone ↔ Watch
  - HealthKit integration (HR, calories)
  - SwiftUI premium UI
  - 9 complication types
  - Always-On Display support

- Paywall screen with RevenueCat integration
- Privacy Policy screen
- App rebranding: tabatago → TabataFit
- Bundle ID: com.millianlmx.tabatafit

## Changes
- New: ios/TabataFit Watch App/ (complete Watch app)
- New: app/paywall.tsx (subscription UI)
- New: app/privacy.tsx (privacy policy)
- New: src/features/watch/ (Watch sync hooks)
- New: admin-web/ (admin dashboard)
- Updated: app.json, package.json (branding)
- Updated: profile.tsx (paywall + privacy links)
- Updated: i18n translations (EN/FR/DE/ES)
- New: app icon 1024x1024

## Watch App Files
- TabataFitWatchApp.swift (entry point)
- ContentView.swift (premium UI)
- HealthKitManager.swift (HR + calories)
- WatchSessionManager.swift (communication)
- Complications/ (WidgetKit)
- UserDefaults+Shared.swift (data sharing)
This commit is contained in:
Millian Lamiaux
2026-03-11 09:43:53 +01:00
parent f80798069b
commit 2ad7ae3a34
86 changed files with 19648 additions and 365 deletions

View File

@@ -0,0 +1,77 @@
"use client";
import Link from "next/link";
import { usePathname, useRouter } from "next/navigation";
import { supabase } from "@/lib/supabase";
import { Button } from "@/components/ui/button";
import {
LayoutDashboard,
Dumbbell,
Users,
FolderOpen,
ImageIcon,
LogOut,
Flame,
} from "lucide-react";
const navItems = [
{ href: "/", label: "Dashboard", icon: LayoutDashboard },
{ href: "/workouts", label: "Workouts", icon: Dumbbell },
{ href: "/trainers", label: "Trainers", icon: Users },
{ href: "/collections", label: "Collections", icon: FolderOpen },
{ href: "/media", label: "Media", icon: ImageIcon },
];
export function Sidebar() {
const pathname = usePathname();
const router = useRouter();
const handleLogout = async () => {
await supabase.auth.signOut();
router.push("/login");
};
return (
<aside className="w-64 bg-neutral-950 border-r border-neutral-800 flex flex-col h-screen sticky top-0">
<div className="p-6 border-b border-neutral-800">
<div className="flex items-center gap-2">
<Flame className="w-6 h-6 text-orange-500" />
<span className="text-lg font-bold text-white">TabataFit</span>
</div>
<p className="text-neutral-500 text-sm mt-1">Admin Dashboard</p>
</div>
<nav className="flex-1 p-4 space-y-1">
{navItems.map((item) => {
const Icon = item.icon;
const isActive = pathname === item.href;
return (
<Link
key={item.href}
href={item.href}
className={`flex items-center gap-3 px-4 py-3 rounded-lg text-sm font-medium transition-colors ${
isActive
? "bg-orange-500/10 text-orange-500"
: "text-neutral-400 hover:text-white hover:bg-neutral-900"
}`}
>
<Icon className="w-5 h-5" />
{item.label}
</Link>
);
})}
</nav>
<div className="p-4 border-t border-neutral-800">
<Button
variant="ghost"
className="w-full justify-start text-neutral-400 hover:text-white"
onClick={handleLogout}
>
<LogOut className="w-5 h-5 mr-3" />
Logout
</Button>
</div>
</aside>
);
}