-
-
Notifications
You must be signed in to change notification settings - Fork 230
Feat/pro preorder #357
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat/pro preorder #357
Changes from all commits
e390b04
719bc9b
0fb8899
4cba206
03e5e05
57b8def
db22a63
64a195e
8b725e7
200b395
9a65396
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| import React from 'react'; | ||
| import { View, Text, TouchableOpacity } from 'react-native'; | ||
| import Icon from 'react-native-vector-icons/Feather'; | ||
| import { AppSheet } from './AppSheet'; | ||
| import { useThemedStyles } from '../theme'; | ||
| import type { ThemeColors, ThemeShadows } from '../theme'; | ||
| import { SPACING, TYPOGRAPHY, PRO_AHA_FEATURES } from '../constants'; | ||
|
|
||
| interface ProAhaSheetProps { | ||
| visible: boolean; | ||
| onClose: () => void; | ||
| onRegister: () => void; | ||
| } | ||
|
|
||
| export const ProAhaSheet: React.FC<ProAhaSheetProps> = ({ visible, onClose, onRegister }) => { | ||
| const styles = useThemedStyles(createStyles); | ||
|
|
||
| const handleCta = () => { | ||
| onClose(); | ||
| onRegister(); | ||
| }; | ||
|
|
||
| return ( | ||
| <AppSheet visible={visible} onClose={onClose} enableDynamicSizing title="Off Grid PRO"> | ||
| <View style={styles.content}> | ||
| <Text style={styles.headline}>Loving Off Grid?</Text> | ||
| <Text style={styles.subheadline}> | ||
| Help us build what's next - and get it free for life. | ||
| </Text> | ||
|
|
||
| <View style={styles.featureList}> | ||
| {PRO_AHA_FEATURES.map(feature => ( | ||
| <View key={feature} style={styles.featureRow}> | ||
| <Icon name="check" size={14} color={styles.checkIcon.color} /> | ||
| <Text style={styles.featureText}>{feature}</Text> | ||
| </View> | ||
| ))} | ||
| </View> | ||
|
|
||
| <Text style={styles.guarantee}> | ||
| Ship in 12 weeks or full refund. No questions asked. | ||
| </Text> | ||
|
|
||
| <TouchableOpacity style={styles.ctaButton} onPress={handleCta}> | ||
| <Text style={styles.ctaText}>I am in 🔥</Text> | ||
|
Comment on lines
+24
to
+45
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| </TouchableOpacity> | ||
| </View> | ||
| </AppSheet> | ||
| ); | ||
| }; | ||
|
|
||
| const createStyles = (colors: ThemeColors, _shadows: ThemeShadows) => ({ | ||
| content: { | ||
| paddingHorizontal: SPACING.xl, | ||
| paddingTop: SPACING.md, | ||
| paddingBottom: SPACING.xxl, | ||
| alignItems: 'center' as const, | ||
| }, | ||
| headline: { | ||
| ...TYPOGRAPHY.h2, | ||
| color: colors.text, | ||
| textAlign: 'center' as const, | ||
| marginBottom: SPACING.sm, | ||
| }, | ||
| subheadline: { | ||
| ...TYPOGRAPHY.body, | ||
| color: colors.textSecondary, | ||
| textAlign: 'center' as const, | ||
| marginBottom: SPACING.md, | ||
| }, | ||
| priceRow: { | ||
| marginBottom: SPACING.lg, | ||
| }, | ||
| price: { | ||
| ...TYPOGRAPHY.bodySmall, | ||
| color: colors.primary, | ||
| textAlign: 'center' as const, | ||
| }, | ||
| featureList: { | ||
| width: '100%' as const, | ||
| marginBottom: SPACING.lg, | ||
| gap: SPACING.sm, | ||
| }, | ||
| featureRow: { | ||
| flexDirection: 'row' as const, | ||
| alignItems: 'center' as const, | ||
| gap: SPACING.sm, | ||
| }, | ||
| checkIcon: { | ||
| color: colors.primary, | ||
| }, | ||
| featureText: { | ||
| ...TYPOGRAPHY.body, | ||
| color: colors.text, | ||
| }, | ||
| guarantee: { | ||
| ...TYPOGRAPHY.bodySmall, | ||
| color: colors.textMuted, | ||
| textAlign: 'center' as const, | ||
| marginBottom: SPACING.lg, | ||
| }, | ||
| ctaButton: { | ||
| width: '100%' as const, | ||
| paddingVertical: SPACING.md, | ||
| backgroundColor: colors.primary, | ||
| borderRadius: 8, | ||
| alignItems: 'center' as const, | ||
| justifyContent: 'center' as const, | ||
| marginBottom: SPACING.sm, | ||
| }, | ||
| ctaText: { | ||
| ...TYPOGRAPHY.body, | ||
| color: colors.background, | ||
| }, | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,8 @@ export type RootStackParamList = { | |
| // Already in RootStack | ||
| DownloadManager: undefined; | ||
| Gallery: { conversationId?: string } | undefined; | ||
| ProDetail: undefined; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make sure to add types
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| About: undefined; | ||
| }; | ||
|
|
||
| // Tab navigator — simple, no sub-stacks | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,166 @@ | ||
| import React from 'react'; | ||
| import { View, Text, TouchableOpacity, Linking, ScrollView, Image, StyleSheet } from 'react-native'; | ||
| import { SafeAreaView } from 'react-native-safe-area-context'; | ||
| import { useNavigation } from '@react-navigation/native'; | ||
| import Icon from 'react-native-vector-icons/Feather'; | ||
| import { useTheme, useThemedStyles } from '../theme'; | ||
| import type { ThemeColors, ThemeShadows } from '../theme'; | ||
| import { SPACING, TYPOGRAPHY } from '../constants'; | ||
| import { MadeWithLove } from '../components/MadeWithLove'; | ||
| import { AnimatedListItem } from '../components/AnimatedListItem'; | ||
| import { useFocusTrigger } from '../hooks/useFocusTrigger'; | ||
| import { GITHUB_URL } from '../utils/sharePrompt'; | ||
| import packageJson from '../../package.json'; | ||
|
|
||
| const WEDNESDAY_MOBILE_URL = 'https://mobile.wednesday.is/hire-ai-native-mobile-squad?utm_source=off-grid-mobile-app&utm_medium=about-screen&utm_campaign=in-app'; | ||
|
|
||
| export const AboutScreen: React.FC = () => { | ||
| const navigation = useNavigation(); | ||
| const { colors } = useTheme(); | ||
| const styles = useThemedStyles(createStyles); | ||
| const focusTrigger = useFocusTrigger(); | ||
|
|
||
| return ( | ||
| <SafeAreaView style={styles.container} edges={['top', 'bottom']}> | ||
| <View style={styles.header}> | ||
| <TouchableOpacity onPress={() => navigation.goBack()} style={styles.backButton}> | ||
| <Icon name="arrow-left" size={20} color={colors.text} /> | ||
| </TouchableOpacity> | ||
| <Text style={styles.headerTitle}>About</Text> | ||
| <View style={styles.backButton} /> | ||
| </View> | ||
|
|
||
| <ScrollView contentContainerStyle={styles.content}> | ||
| {/* App identity */} | ||
| <View style={styles.heroSection}> | ||
| <Image source={require('../assets/logo.png')} style={staticStyles.appIcon} /> | ||
| <Text style={styles.appName}>Off Grid</Text> | ||
| <Text style={styles.version}>Version {packageJson.version}</Text> | ||
| <Text style={styles.description}> | ||
| Local AI that runs entirely on your phone. No cloud, no telemetry, nothing leaves the device. | ||
| </Text> | ||
| </View> | ||
|
|
||
| {/* Open Source row */} | ||
| <View style={styles.navSection}> | ||
| <AnimatedListItem | ||
| index={0} | ||
| staggerMs={40} | ||
| trigger={focusTrigger} | ||
| style={[styles.navItem, styles.navItemLast]} | ||
| onPress={() => Linking.openURL(GITHUB_URL)} | ||
| > | ||
| <View style={styles.navItemIcon}> | ||
| <Icon name="github" size={16} color={colors.textSecondary} /> | ||
| </View> | ||
| <View style={styles.navItemContent}> | ||
| <Text style={styles.navItemTitle}>Open Source</Text> | ||
| <Text style={styles.navItemDesc}>View the source on GitHub</Text> | ||
| </View> | ||
| <Icon name="external-link" size={14} color={colors.textMuted} /> | ||
| </AnimatedListItem> | ||
| </View> | ||
|
|
||
| {/* Built by Wednesday row */} | ||
| <View style={styles.navSection}> | ||
| <AnimatedListItem | ||
| index={1} | ||
| staggerMs={40} | ||
| trigger={focusTrigger} | ||
| style={[styles.navItem, styles.navItemLast]} | ||
| onPress={() => Linking.openURL(WEDNESDAY_MOBILE_URL)} | ||
| > | ||
| <View style={styles.navItemIcon}> | ||
| <Image source={require('../assets/wednesday_logo.png')} style={styles.wednesdayLogo} /> | ||
| </View> | ||
| <View style={styles.navItemContent}> | ||
| <Text style={styles.navItemTitle}>Built by Wednesday</Text> | ||
| <Text style={styles.navItemDesc}>We build mobile apps for enterprise teams</Text> | ||
| </View> | ||
| <Icon name="external-link" size={14} color={colors.textMuted} /> | ||
| </AnimatedListItem> | ||
| </View> | ||
| </ScrollView> | ||
|
|
||
| {/* Pinned footer */} | ||
| <MadeWithLove /> | ||
| </SafeAreaView> | ||
| ); | ||
| }; | ||
|
|
||
| const staticStyles = StyleSheet.create({ | ||
| appIcon: { width: 72, height: 72, borderRadius: 16, marginBottom: SPACING.md }, | ||
| }); | ||
|
|
||
| const createStyles = (colors: ThemeColors, shadows: ThemeShadows) => ({ | ||
| container: { flex: 1, backgroundColor: colors.background }, | ||
| header: { | ||
| flexDirection: 'row' as const, | ||
| alignItems: 'center' as const, | ||
| justifyContent: 'space-between' as const, | ||
| paddingHorizontal: SPACING.lg, | ||
| paddingVertical: SPACING.md, | ||
| minHeight: 60, | ||
| borderBottomWidth: 1, | ||
| borderBottomColor: colors.border, | ||
| backgroundColor: colors.surface, | ||
| ...shadows.small, | ||
| zIndex: 1, | ||
| }, | ||
| backButton: { width: 36, padding: SPACING.xs }, | ||
| headerTitle: { ...TYPOGRAPHY.h2, color: colors.text }, | ||
| content: { | ||
| paddingHorizontal: SPACING.lg, | ||
| paddingTop: SPACING.lg, | ||
| paddingBottom: SPACING.xxl, | ||
| }, | ||
| heroSection: { | ||
| alignItems: 'center' as const, | ||
| paddingVertical: SPACING.xxl, | ||
| marginBottom: SPACING.xl, | ||
| }, | ||
| appName: { | ||
| ...TYPOGRAPHY.h1, | ||
| color: colors.text, | ||
| marginBottom: SPACING.xs, | ||
| }, | ||
| version: { | ||
| ...TYPOGRAPHY.meta, | ||
| color: colors.textMuted, | ||
| marginBottom: SPACING.md, | ||
| }, | ||
| description: { | ||
| ...TYPOGRAPHY.body, | ||
| color: colors.textSecondary, | ||
| textAlign: 'center' as const, | ||
| lineHeight: 22, | ||
| paddingHorizontal: SPACING.md, | ||
| }, | ||
| navSection: { | ||
| backgroundColor: colors.surface, | ||
| borderRadius: 8, | ||
| marginBottom: SPACING.lg, | ||
| overflow: 'hidden' as const, | ||
| ...shadows.small, | ||
| }, | ||
| navItem: { | ||
| flexDirection: 'row' as const, | ||
| alignItems: 'center' as const, | ||
| padding: SPACING.md, | ||
| borderBottomWidth: 1, | ||
| borderBottomColor: colors.border, | ||
| }, | ||
| navItemLast: { borderBottomWidth: 0 }, | ||
| navItemIcon: { | ||
| width: 28, | ||
| height: 28, | ||
| borderRadius: 6, | ||
| alignItems: 'center' as const, | ||
| justifyContent: 'center' as const, | ||
| marginRight: SPACING.md, | ||
| }, | ||
| navItemContent: { flex: 1 }, | ||
| navItemTitle: { ...TYPOGRAPHY.body, color: colors.text }, | ||
| navItemDesc: { ...TYPOGRAPHY.bodySmall, color: colors.textMuted, marginTop: 2 }, | ||
| wednesdayLogo: { width: 24, height: 24, resizeMode: 'contain' as const }, | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove duplicate imports
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed — merged the two
../themeimports into one.