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:
130
admin-web/lib/supabase.ts
Normal file
130
admin-web/lib/supabase.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL === 'your_supabase_project_url'
|
||||
? 'http://localhost:54321'
|
||||
: (process.env.NEXT_PUBLIC_SUPABASE_URL || 'http://localhost:54321')
|
||||
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY === 'your_supabase_anon_key'
|
||||
? 'placeholder-key'
|
||||
: (process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || 'placeholder-key')
|
||||
|
||||
export const supabase = createClient(supabaseUrl, supabaseKey)
|
||||
|
||||
export const isSupabaseConfigured = () => {
|
||||
const url = process.env.NEXT_PUBLIC_SUPABASE_URL
|
||||
return url !== 'your_supabase_project_url' && url !== 'http://localhost:54321' && !!url
|
||||
}
|
||||
|
||||
export type Json =
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null
|
||||
| { [key: string]: Json | undefined }
|
||||
| Json[]
|
||||
|
||||
export interface Database {
|
||||
public: {
|
||||
Tables: {
|
||||
workouts: {
|
||||
Row: {
|
||||
id: string
|
||||
title: string
|
||||
trainer_id: string
|
||||
category: 'full-body' | 'core' | 'upper-body' | 'lower-body' | 'cardio'
|
||||
level: 'Beginner' | 'Intermediate' | 'Advanced'
|
||||
duration: number
|
||||
calories: number
|
||||
rounds: number
|
||||
prep_time: number
|
||||
work_time: number
|
||||
rest_time: number
|
||||
equipment: string[]
|
||||
music_vibe: 'electronic' | 'hip-hop' | 'pop' | 'rock' | 'chill'
|
||||
exercises: { name: string; duration: number }[]
|
||||
thumbnail_url: string | null
|
||||
video_url: string | null
|
||||
is_featured: boolean
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
Insert: {
|
||||
id?: string
|
||||
title: string
|
||||
trainer_id: string
|
||||
category: 'full-body' | 'core' | 'upper-body' | 'lower-body' | 'cardio'
|
||||
level: 'Beginner' | 'Intermediate' | 'Advanced'
|
||||
duration: number
|
||||
calories: number
|
||||
rounds: number
|
||||
prep_time: number
|
||||
work_time: number
|
||||
rest_time: number
|
||||
equipment?: string[]
|
||||
music_vibe: 'electronic' | 'hip-hop' | 'pop' | 'rock' | 'chill'
|
||||
exercises: { name: string; duration: number }[]
|
||||
thumbnail_url?: string | null
|
||||
video_url?: string | null
|
||||
is_featured?: boolean
|
||||
}
|
||||
Update: Partial<Database['public']['Tables']['workouts']['Insert']>
|
||||
}
|
||||
trainers: {
|
||||
Row: {
|
||||
id: string
|
||||
name: string
|
||||
specialty: string
|
||||
color: string
|
||||
avatar_url: string | null
|
||||
workout_count: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
Insert: {
|
||||
id?: string
|
||||
name: string
|
||||
specialty: string
|
||||
color: string
|
||||
avatar_url?: string | null
|
||||
workout_count?: number
|
||||
}
|
||||
Update: Partial<Omit<Database['public']['Tables']['trainers']['Insert'], 'id'>>
|
||||
}
|
||||
collections: {
|
||||
Row: {
|
||||
id: string
|
||||
title: string
|
||||
description: string
|
||||
icon: string
|
||||
gradient: string[] | null
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
Insert: {
|
||||
id?: string
|
||||
title: string
|
||||
description: string
|
||||
icon: string
|
||||
gradient?: string[] | null
|
||||
}
|
||||
Update: Partial<Omit<Database['public']['Tables']['collections']['Insert'], 'id'>>
|
||||
}
|
||||
collection_workouts: {
|
||||
Row: {
|
||||
id: string
|
||||
collection_id: string
|
||||
workout_id: string
|
||||
sort_order: number
|
||||
}
|
||||
}
|
||||
admin_users: {
|
||||
Row: {
|
||||
id: string
|
||||
email: string
|
||||
role: 'admin' | 'editor'
|
||||
created_at: string
|
||||
last_login: string | null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
6
admin-web/lib/utils.ts
Normal file
6
admin-web/lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
Reference in New Issue
Block a user