Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions packages/demo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ The backend is located at:

`packages/demo/packages/backend`

The web demo is located at:

`packages/demo/packages/frontend`

The Expo React Native demo is located at:

`packages/demo/packages/mobile`


---

Expand Down Expand Up @@ -39,4 +47,64 @@ You can create them manually or let the application initialize them on first run

---

## 📱 Expo Demo

The mobile demo uses Expo and `@flowerforce/flowerbase-client` to test:

- email/password registration
- email/password login
- password reset demo flow
- basic CRUD on `todos`

Environment variables live in:

`packages/demo/packages/mobile/.env`

Expected variables:

- `EXPO_PUBLIC_APP_ID`
- `EXPO_PUBLIC_SERVER_URL`
- `EXPO_PUBLIC_DB_NAME`

For simulators running on the same machine, `http://localhost:3000` is fine.

For Expo Go on a physical device, set `EXPO_PUBLIC_SERVER_URL` to your computer LAN IP, for example `http://192.168.1.10:3000`.

Password reset in the mobile demo works in two steps:

1. it calls the normal Flowerbase reset endpoint
2. it loads `token` and `tokenId` from the demo-only endpoint `/app/<appId>/endpoint/demo-reset-preview`

That preview endpoint exists only to make the Expo demo testable without integrating a real mail provider.

---

## ✅ Demo E2E Tests

The mobile package includes a Jest E2E test that verifies:

- register
- login
- create/read/update/delete on `todos`
- password reset
- login with the new password

Run it from:

```bash
cd packages/demo
npm run test:e2e
```

Start the demo backend before running the Jest test.

If you want a single command that starts the backend and then runs the mobile Jest suite:

```bash
cd packages/demo
npm run test:e2e:with-backend
```

Use Node 20+ when running it.

---
7 changes: 5 additions & 2 deletions packages/demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@
"scripts": {
"start": "concurrently \"npm run start:frontend\" \"npm run start:backend\"",
"start:frontend": "cd packages/frontend && npm run dev",
"start:mobile": "cd packages/mobile && npm start",
"start:backend": "cd packages/backend && npm run start",
"build:backend": "cd packages/backend && npm run build"
"build:backend": "cd packages/backend && npm run build",
"test:e2e": "cd packages/mobile && npm run test:e2e",
"test:e2e:with-backend": "npm run build:backend && concurrently -k -s first \"cd packages/backend && /Users/andreazucca/.nvm/versions/node/v25.7.0/bin/node dist/index.js\" \"sh -c 'until curl -sS -o /dev/null -X POST http://localhost:3000/app/flowerbase-demo/endpoint/webhooks/searchTodos; do sleep 1; done; cd packages/mobile && /Users/andreazucca/.nvm/versions/node/v25.7.0/bin/node ./node_modules/jest/bin/jest.js --config jest.e2e.config.cjs --runInBand'\""
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"concurrently": "^9.1.2"
}
}
}
4 changes: 3 additions & 1 deletion packages/demo/packages/backend/auth/providers.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
"disabled": false,
"config": {
"autoConfirm": true,
"resetFunctionName": "resetPasswordHandler",
"resetPasswordSubject": "reset",
"resetPasswordUrl": "http://localhost:5173/password-reset",
"runConfirmationFunction": false
"runConfirmationFunction": false,
"runResetFunction": true
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,17 @@
}
}
],
"roles": []
}
"roles": [
{
"name": "todoOwner",
"apply_when": {
"userId": "%%user.id"
},
"insert": true,
"delete": true,
"search": true,
"read": true,
"write": true
}
]
}
14 changes: 13 additions & 1 deletion packages/demo/packages/backend/functions/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,17 @@
"private": false,
"run_as_system": false,
"disable_arg_logs": true
},
{
"name": "resetPasswordHandler",
"private": false,
"run_as_system": true,
"disable_arg_logs": true
},
{
"name": "getResetPasswordPreview",
"private": false,
"run_as_system": true,
"disable_arg_logs": true
}
]
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module.exports = async function (payload, response) {
const email = payload?.query?.email

if (!email || typeof email !== 'string') {
response.setStatusCode(400)
return { message: 'Missing email query parameter' }
}

const resetRequest = await context.services
.get('mongodb-atlas')
.db('flowerbase-demo')
.collection('reset_password_requests')
.findOne({ email })

if (!resetRequest) {
response.setStatusCode(404)
return { message: 'No reset request found for this email' }
}

return {
email,
token: resetRequest.token,
tokenId: resetRequest.tokenId,
createdAt: resetRequest.createdAt
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = async function ({ token, tokenId, email }) {
if (!token || !tokenId || !email) {
throw new Error('Missing reset params')
}

return { status: 'pending' }
}
12 changes: 11 additions & 1 deletion packages/demo/packages/backend/http_endpoints/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,15 @@
"fetch_custom_user_data": false,
"create_user_on_auth": false,
"disabled": false
},
{
"http_method": "GET",
"route": "/demo-reset-preview",
"function_name": "getResetPasswordPreview",
"validation_method": "NO_VALIDATION",
"respond_result": true,
"fetch_custom_user_data": false,
"create_user_on_auth": false,
"disabled": false
}
]
]
4 changes: 2 additions & 2 deletions packages/demo/packages/backend/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@
"allowJs": true,
"checkJs": false
},
"include": ["**/*.ts", "**/*.json", "**/*.txt"]
}
"include": ["**/*.ts", "**/*.js", "**/*.json", "**/*.txt"]
}
4 changes: 4 additions & 0 deletions packages/demo/packages/mobile/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
.expo
dist
web-build
68 changes: 68 additions & 0 deletions packages/demo/packages/mobile/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { StatusBar } from 'expo-status-bar'
import { ActivityIndicator, SafeAreaView, StyleSheet, Text, View } from 'react-native'
import { AuthView } from './src/views/AuthView'
import { ResetPasswordView } from './src/views/ResetPasswordView'
import { TodosView } from './src/views/TodosView'
import { usePasswordReset } from './src/hooks/usePasswordReset'
import { useSession } from './src/hooks/useSession'
import { useTodos } from './src/hooks/useTodos'

export default function App() {
const session = useSession()
const passwordReset = usePasswordReset()
const todos = useTodos(session.user?.id ?? null)

if (session.isBootstrapping) {
return (
<SafeAreaView style={styles.screen}>
<StatusBar style="dark" />
<View style={styles.centered}>
<ActivityIndicator size="large" color="#0f766e" />
<Text style={styles.bootText}>Restoring Flowerbase session...</Text>
</View>
</SafeAreaView>
)
}

return (
<SafeAreaView style={styles.screen}>
<StatusBar style="dark" />
{session.user ? (
<TodosView session={session} todos={todos} />
) : session.mode === 'reset' ? (
<ResetPasswordView
mode={session.mode}
onSwitchMode={session.setMode}
passwordReset={passwordReset}
/>
) : (
<AuthView
mode={session.mode}
error={session.error}
isSubmitting={session.isSubmitting}
onLogin={session.login}
onRegister={session.register}
onSwitchMode={session.setMode}
/>
)}
</SafeAreaView>
)
}

const styles = StyleSheet.create({
screen: {
flex: 1,
backgroundColor: '#f4efe6'
},
centered: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
gap: 16,
paddingHorizontal: 24
},
bootText: {
fontSize: 16,
color: '#3f3a34'
}
})
15 changes: 15 additions & 0 deletions packages/demo/packages/mobile/app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"expo": {
"name": "Flowerbase Mobile Demo",
"slug": "flowerbase-mobile-demo",
"version": "1.0.0",
"orientation": "portrait",
"userInterfaceStyle": "light",
"ios": {
"supportsTablet": true
},
"android": {
"predictiveBackGestureEnabled": false
}
}
}
6 changes: 6 additions & 0 deletions packages/demo/packages/mobile/jest.e2e.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
testEnvironment: 'node',
testMatch: ['<rootDir>/src/__tests__/e2e.test.cjs'],
testTimeout: 120000,
verbose: true
}
18 changes: 18 additions & 0 deletions packages/demo/packages/mobile/metro.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const path = require('path')
const { getDefaultConfig } = require('expo/metro-config')

const projectRoot = __dirname
const workspaceRoot = path.resolve(projectRoot, '../../../..')

const config = getDefaultConfig(projectRoot)

config.watchFolders = [workspaceRoot]
config.resolver.nodeModulesPaths = [
path.resolve(projectRoot, 'node_modules'),
path.resolve(workspaceRoot, 'node_modules')
]
config.resolver.extraNodeModules = {
'@flowerforce/flowerbase-client': path.resolve(workspaceRoot, 'packages/flowerbase-client')
}

module.exports = config
Loading
Loading