diff --git a/.eas/workflows/deploy.yml b/.eas/workflows/deploy.yml new file mode 100644 index 0000000..dbbc0e8 --- /dev/null +++ b/.eas/workflows/deploy.yml @@ -0,0 +1,13 @@ +name: Deploy + +on: + push: + branches: ['main'] + +jobs: + deploy: + type: deploy + name: Deploy + environment: production + params: + prod: true \ No newline at end of file diff --git a/.eas/workflows/development-build.yml b/.eas/workflows/development-build.yml new file mode 100644 index 0000000..3b05724 --- /dev/null +++ b/.eas/workflows/development-build.yml @@ -0,0 +1,18 @@ +name: Development Build + +jobs: + android_development_build: + type: build + params: + platform: android + profile: development + ios_device_development_build: + type: build + params: + platform: ios + profile: development + ios_simulator_development_build: + type: build + params: + platform: ios + profile: development-simulator diff --git a/.env b/.env new file mode 100644 index 0000000..8738948 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +EXPO_PUBLIC_NAME_FLAME_ENDPOINT=https://name-flame-server-1090437595615.us-central1.run.app \ No newline at end of file diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml new file mode 100644 index 0000000..1928ee6 --- /dev/null +++ b/.github/workflows/preview.yml @@ -0,0 +1,40 @@ +name: preview +on: pull_request + +jobs: + update: + name: EAS Update + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + steps: + - name: Check for EXPO_TOKEN + run: | + if [ -z "${{ secrets.EXPO_TOKEN }}" ]; then + echo "You must provide an EXPO_TOKEN secret linked to this project's Expo account in this repo's secrets. Learn more: https://docs.expo.dev/eas-update/github-actions" + exit 1 + fi + + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22.x + cache: npm + + - name: Setup EAS + uses: expo/expo-github-action@v8 + with: + eas-version: latest + token: ${{ secrets.EXPO_TOKEN }} + + - name: Install dependencies + run: npm install + + - name: Create preview + uses: expo/expo-github-action/preview@v8 + with: + command: eas update --auto diff --git a/app.json b/app.json index b5d007f..a8f37f9 100644 --- a/app.json +++ b/app.json @@ -9,13 +9,18 @@ "userInterfaceStyle": "automatic", "newArchEnabled": true, "ios": { - "supportsTablet": true + "supportsTablet": true, + "bundleIdentifier": "com.builtbybennett.nameflame", + "infoPlist": { + "ITSAppUsesNonExemptEncryption": false + } }, "android": { "adaptiveIcon": { "foregroundImage": "./assets/images/adaptive-icon.png", "backgroundColor": "#ffffff" - } + }, + "package": "com.builtbybennett.nameflame" }, "web": { "bundler": "metro", @@ -32,6 +37,12 @@ "resizeMode": "contain", "backgroundColor": "#ffffff" } + ], + [ + "expo-font", + { + "fonts": ["./assets/fonts/BricolageGrotesque.ttf"] + } ] ], "experiments": { diff --git a/app/(app)/_layout.tsx b/app/(app)/_layout.tsx index b797ced..dd57ee9 100644 --- a/app/(app)/_layout.tsx +++ b/app/(app)/_layout.tsx @@ -1,17 +1,26 @@ -import { Text } from 'react-native'; +import { ActivityIndicator } from 'react-native'; import { Redirect } from 'expo-router'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { Drawer } from 'expo-router/drawer'; +import { Colors } from '../../constants/Colors'; +import { MaterialIcons } from '@expo/vector-icons'; +import { useRouter, useLocalSearchParams } from 'expo-router'; import { useToken } from '../../contexts/authCtx'; +import { ThemedView } from '@/components/ThemedView'; export default function AppLayout() { const { token, isLoading } = useToken(); + const router = useRouter(); + const { id } = useLocalSearchParams<{ id: string }>(); + // You can keep the splash screen open, or render a loading screen like we do here. if (isLoading) { - return Loading...; + return + + ; } // Only require authentication within the (app) group's layout as users @@ -25,11 +34,95 @@ export default function AppLayout() { // This layout can be deferred because it's not the root layout. return ( - - + + + ( + router.push('./nameContext/new')} + style={{ marginRight: 10 }} + /> + ), + }} + /> + ( + router.back()} + style={{ marginLeft: 10 }} + /> + ), + headerRight: () => ( + id === 'new' + ? null + : ( + alert('Delete')} + style={{ marginRight: 10 }} + />) + ), + }} + /> + + diff --git a/app/(app)/index.js b/app/(app)/index.js index 3110794..d3b1ff3 100644 --- a/app/(app)/index.js +++ b/app/(app)/index.js @@ -1,11 +1,5 @@ -import { Text, View } from 'react-native'; +import { Redirect } from 'expo-router'; export default function Index() { - return ( - - - App Landing - - - ); + return ; } \ No newline at end of file diff --git a/app/(app)/match/index.js b/app/(app)/match/index.js deleted file mode 100644 index df69c09..0000000 --- a/app/(app)/match/index.js +++ /dev/null @@ -1,11 +0,0 @@ -import { Text, View } from 'react-native'; - -export default function Index() { - return ( - - - Match View - - - ); -} diff --git a/app/(app)/nameContext/[id].tsx b/app/(app)/nameContext/[id].tsx index 535fb10..fa5c76d 100644 --- a/app/(app)/nameContext/[id].tsx +++ b/app/(app)/nameContext/[id].tsx @@ -1,9 +1,224 @@ -import { Text, View } from 'react-native'; +import React, { useState, useEffect } from 'react'; +import { Text, View, TextInput, StyleSheet, TouchableOpacity, ActivityIndicator } from 'react-native'; +import { useLocalSearchParams } from 'expo-router'; +import { ThemedView } from '@/components/ThemedView'; +import { Dropdown } from 'react-native-element-dropdown'; +import { Colors } from '@/constants/Colors'; +import useApi from '@/hooks/useApi'; export default function NameContextDetailsView() { + const { id } = useLocalSearchParams<{ id: string }>(); + const api = useApi(); + const [loading, setLoading] = useState(true); + // form values + const [nameValue, setNameValue] = useState(''); + const [descriptionValue, setDescriptionValue] = useState(''); + const [nounValue, setNounValue] = useState(''); + const [maxCharacters, setMaxCharacters] = useState(''); + const [genderValue, setGenderValue] = useState('neutral'); + const [startsWithValue, setStartsWithValue] = useState(''); + const [participants, setParticipants] = useState([]); + + const isExistingNameContxt = id !== 'new'; + + function resetForm() { + setNameValue(''); + setDescriptionValue(''); + setNounValue(''); + setMaxCharacters(''); + setGenderValue('neutral'); + setStartsWithValue(''); + setParticipants([]); + } + + useEffect(() => { + if (isExistingNameContxt) { + fetchNameContext(); + } + else { + resetForm(); + setLoading(false); + } + }, [id]); + + function fetchNameContext() { + setLoading(true); + + api.get(`/nameContext/${id}`).then((resp) => { + const data = resp.data; + setNameValue(data.name); + setDescriptionValue(data.description); + setNounValue(data.noun); + setMaxCharacters(data.filter.maxCharacters); + setGenderValue(data.gender); + setLoading(false); + }).catch((err) => { + alert(err); + setLoading(false); + }); + } + + function saveNameContext() { + api.post('/nameContext', { + name: nameValue, + description: descriptionValue, + noun: nounValue, + filter: { + gender: genderValue, + maxCharacters, + startsWithLetter: startsWithValue + }, + participants + }).then(() => { + alert('Name context created'); + }).catch((err) => { + console.log(err); + alert('Failed to create name context'); + }); + } + + if (loading) { + return + + + } + return ( - - Hello from Name Context Details - + + + Basic Information + + Name + + + Description + + + Noun + + + Max Characters + + + Gender + { + setGenderValue(item.value); + }} + /> + + Starts with the letter + setStartsWithValue(String(value).toUpperCase())} + maxLength={1} + /> + + + Save + + ); -} \ No newline at end of file +} + +/* // Participants + + Participants + { + setParticipants(item.value); + }} + /> + +*/ + +const styles = StyleSheet.create({ + container: { + padding: 20, + marginHorizontal: 10, + marginVertical: 20, + width: '90%', + color: Colors.core.black, + borderRadius: 10, + backgroundColor: Colors.core.tan, + shadowColor: Colors.core.black, + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.25, + shadowRadius: 3.84, + elevation: 5, // for Android + }, + label: { + fontFamily: 'Bricolage-Grotesque', + fontWeight: '600', + fontSize: 16, + marginTop: 10 + }, + input: { + backgroundColor: Colors.core.white, + padding: 5, + fontFamily: 'Bricolage-Grotesque', + borderRadius: 5, + }, + buttons: { + backgroundColor: Colors.core.orange, + padding: 10, + borderRadius: 5, + marginTop: 10, + alignItems: 'center', + width: '90%' + }, + buttonText: { + color: Colors.core.white, + fontSize: 16, + }, +}); \ No newline at end of file diff --git a/app/(app)/nameContext/index.tsx b/app/(app)/nameContext/index.tsx index c374235..a3c3a9e 100644 --- a/app/(app)/nameContext/index.tsx +++ b/app/(app)/nameContext/index.tsx @@ -1,9 +1,57 @@ -import { Text, View } from 'react-native'; +import React from 'react'; +import { ScrollView, RefreshControl, ActivityIndicator } from 'react-native'; +import { ThemedView } from '@/components/ThemedView'; +import { NameContextListItem } from '@/components/NameContextListItem'; +import { Colors } from '@/constants/Colors'; + +import { NameContext } from '@/types/NameContext'; + +import useApi from '@/hooks/useApi'; export default function NameContextListView() { + const api = useApi(); + + const [refreshing, setRefreshing] = React.useState(false); + + const [nameContexts, setNameContexts] = React.useState([]); + + function fetchNameContexts() { + setRefreshing(true); + api.get('/nameContexts').then((resp) => { + setNameContexts(resp.data); + console.log(resp); + setRefreshing(false); + }).catch((err) => { + setRefreshing(false); + alert(err); + }) + } + + React.useEffect(() => { + fetchNameContexts(); + }, []) + + if (refreshing) { + return + + + } + return ( - - Hello from Name Context - + + }> + {nameContexts.map(nameContext => { + return + })} + + ); } diff --git a/app/(app)/settings/index.js b/app/(app)/settings/index.js index fcc3164..eaba31d 100644 --- a/app/(app)/settings/index.js +++ b/app/(app)/settings/index.js @@ -1,18 +1,48 @@ -import { Text, View } from 'react-native'; +import { useState, useEffect } from "react"; +import { View, Button, Switch } from 'react-native'; +import { Colors } from '../../../constants/Colors'; +import { ThemedView } from '../../../components/ThemedView'; +import { ThemedText } from '../../../components/ThemedText'; import { useToken } from '../../../contexts/authCtx'; -export default function Index() { +export default function SettingsView() { const { signOut } = useToken(); + + const [sendNotifications, setSendNotifications] = useState(true); + return ( - - { - // The `app/(app)/_layout.tsx` will redirect to the sign-in screen. - signOut(); - }}> - Settings Page, Sign Out - - + + + + Settings + + + + + Send Notifications + + + +