This commit is contained in:
Jamie Brunton committed 2025-12-31 12:20:23 +00:00
1 parent f46f0d59df
commit 3ca3da4501
5 files changed
+40 -2

No files matched your search

+22
View File
@@ -95,6 +95,7 @@ Edge functions in `apps/supabase/functions/` handle complex operations:
- `send-notification` - Push notifications for matches - `send-notification` - Push notifications for matches
- `revenuecat-webhook` - Subscription webhook handler - `revenuecat-webhook` - Subscription webhook handler
- `sync-subscription` - Syncs RevenueCat subscription status - `sync-subscription` - Syncs RevenueCat subscription status
- `redeem-code` - Redeems promotional codes for premium access (website only)
#### Edge Function Deployment Guidelines #### Edge Function Deployment Guidelines
When deploying edge functions via MCP tools (`mcp__sauci-*__deploy_edge_function`): When deploying edge functions via MCP tools (`mcp__sauci-*__deploy_edge_function`):
@@ -118,6 +119,7 @@ This approach:
| `submit-response` | false | Manual via getUser() | | `submit-response` | false | Manual via getUser() |
| `sync-subscription` | false | Manual via getUser() | | `sync-subscription` | false | Manual via getUser() |
| `delete-relationship` | false | Manual via getUser() | | `delete-relationship` | false | Manual via getUser() |
| `redeem-code` | false | Manual via getUser() |
| `revenuecat-webhook` | false | Webhook signature validation | | `revenuecat-webhook` | false | Webhook signature validation |
| `send-notification` | false | Internal trigger (no auth) | | `send-notification` | false | Internal trigger (no auth) |
| `send-message-notification` | false | Internal trigger (no auth) | | `send-message-notification` | false | Internal trigger (no auth) |
@@ -134,6 +136,26 @@ This approach:
- RevenueCat handles in-app purchases and subscription management - RevenueCat handles in-app purchases and subscription management
- RLS policies enforce couple-level data isolation - RLS policies enforce couple-level data isolation
### Redemption Code System
Promotional codes that grant users premium access. **Codes can only be redeemed on the website, NOT in the mobile app.**
**Database Tables:**
- `redemption_codes` - Stores codes with max_uses, current_uses, expiration, active status
- `code_redemptions` - Tracks which users redeemed which codes
**Components:**
- **Admin UI** (`apps/admin/src/pages/RedemptionCodesPage.tsx`) - Super admins can generate, manage, and view redemption codes
- **Edge Function** (`apps/supabase/functions/redeem-code/`) - Validates and processes code redemption
- **Database Function** (`redeem_code(p_code)`) - Atomic redemption with validation (active, not expired, has uses remaining, user hasn't redeemed)
**Flow:**
1. Admin generates code in admin dashboard
2. User enters code on website (NOT mobile app)
3. `redeem-code` edge function calls `redeem_code()` database function
4. On success, user's `profiles.is_premium` is set to `true`
**Important:** Do NOT add redemption code UI to the mobile app. Apple requires in-app purchases for premium features purchased within iOS apps.
## Mobile UI - DO NOT CHANGE ## Mobile UI - DO NOT CHANGE
### Tab Bar Background ### Tab Bar Background
+9
View File
@@ -12,6 +12,7 @@ import { UserDetailPage } from '@/pages/users/UserDetailPage';
import { MatchChatPage } from '@/pages/users/MatchChatPage'; import { MatchChatPage } from '@/pages/users/MatchChatPage';
import { AuditLogsPage } from '@/pages/AuditLogsPage'; import { AuditLogsPage } from '@/pages/AuditLogsPage';
import { AdminsPage } from '@/pages/admins/AdminsPage'; import { AdminsPage } from '@/pages/admins/AdminsPage';
import { RedemptionCodesPage } from '@/pages/RedemptionCodesPage';
function App() { function App() {
return ( return (
@@ -72,6 +73,14 @@ function App() {
</ProtectedRoute> </ProtectedRoute>
} }
/> />
<Route
path="/redemption-codes"
element={
<ProtectedRoute requireSuperAdmin>
<RedemptionCodesPage />
</ProtectedRoute>
}
/>
<Route <Route
path="/audit-logs" path="/audit-logs"
element={ element={
@@ -10,6 +10,7 @@ import {
LogOut, LogOut,
Sparkles, Sparkles,
Shield, Shield,
Ticket,
} from 'lucide-react'; } from 'lucide-react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Separator } from '@/components/ui/separator'; import { Separator } from '@/components/ui/separator';
@@ -47,6 +48,12 @@ const systemNav: NavItem[] = [
icon: <Shield className="h-5 w-5" />, icon: <Shield className="h-5 w-5" />,
requireSuperAdmin: true, requireSuperAdmin: true,
}, },
{
label: 'Redemption Codes',
href: '/redemption-codes',
icon: <Ticket className="h-5 w-5" />,
requireSuperAdmin: true,
},
{ {
label: 'Audit Logs', label: 'Audit Logs',
href: '/audit-logs', href: '/audit-logs',
@@ -13,6 +13,8 @@ const AUDITED_TABLES = [
'subscriptions', 'subscriptions',
'feedback', 'feedback',
'admin_users', 'admin_users',
'redemption_codes',
'code_redemptions',
]; ];
type AuditAction = 'INSERT' | 'UPDATE' | 'DELETE'; type AuditAction = 'INSERT' | 'UPDATE' | 'DELETE';
-2
View File
@@ -109,8 +109,6 @@ android {
signingConfig signingConfigs.debug signingConfig signingConfigs.debug
} }
release { release {
// Caution! In production, you need to generate your own keystore file.
// see https://reactnative.dev/docs/signed-apk-android.
signingConfig signingConfigs.debug signingConfig signingConfigs.debug
shrinkResources (findProperty('android.enableShrinkResourcesInReleaseBuilds')?.toBoolean() ?: false) shrinkResources (findProperty('android.enableShrinkResourcesInReleaseBuilds')?.toBoolean() ?: false)
minifyEnabled enableProguardInReleaseBuilds minifyEnabled enableProguardInReleaseBuilds