Skip to content
Merged
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
66 changes: 66 additions & 0 deletions packages/mobile-sdk-demo/src/components/LogsPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.

import React from 'react';
import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';

type Props = {
logs: string[];
show: boolean;
onToggle: () => void;
};

export default function LogsPanel({ logs, show, onToggle }: Props) {
if (logs.length === 0) return null;
return (
<View style={styles.container}>
<TouchableOpacity onPress={onToggle} style={styles.toggle}>
<Text style={styles.toggleText}>{show ? `▼ Hide Logs (${logs.length})` : `▶ Show Logs (${logs.length})`}</Text>
</TouchableOpacity>
{show && (
<ScrollView style={styles.logs} nestedScrollEnabled>
{logs.map((log, idx) => (
<Text key={idx} style={styles.entry}>
{log}
</Text>
))}
</ScrollView>
)}
</View>
);
}

const styles = StyleSheet.create({
container: {
marginTop: 8,
},
toggle: {
padding: 8,
backgroundColor: '#fff',
borderRadius: 4,
borderWidth: 1,
borderColor: '#ffc107',
},
toggleText: {
fontSize: 12,
color: '#856404',
textAlign: 'center',
fontWeight: '600',
},
logs: {
marginTop: 8,
maxHeight: 200,
backgroundColor: '#fff',
borderRadius: 4,
borderWidth: 1,
borderColor: '#ffc107',
padding: 8,
},
entry: {
fontSize: 11,
fontFamily: 'monospace',
color: '#333',
marginBottom: 4,
},
});
117 changes: 117 additions & 0 deletions packages/mobile-sdk-demo/src/components/MenuButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.

import React from 'react';
import { StyleSheet, Text, TouchableOpacity } from 'react-native';

type Props = {
title: string;
subtitle?: string;
onPress: () => void;
isWorking?: boolean;
disabled?: boolean;
};

export default function MenuButton({ title, subtitle, onPress, isWorking = false, disabled = false }: Props) {
return (
<TouchableOpacity
style={[
styles.menuButton,
isWorking ? styles.workingButton : styles.placeholderButton,
disabled && styles.disabledButton,
]}
onPress={onPress}
activeOpacity={0.7}
disabled={disabled}
>
<Text
style={[
styles.menuButtonText,
isWorking ? styles.workingButtonText : styles.placeholderButtonText,
disabled && styles.disabledButtonText,
]}
>
{title}
</Text>
{subtitle ? (
<Text
style={[
styles.menuButtonSubtitle,
disabled
? styles.disabledSubtitleText
: isWorking
? styles.workingButtonSubtitle
: styles.placeholderButtonSubtitle,
]}
>
{subtitle}
</Text>
) : null}
</TouchableOpacity>
);
}

const styles = StyleSheet.create({
menuButton: {
width: '100%',
paddingVertical: 16,
paddingHorizontal: 24,
borderRadius: 16,
marginBottom: 16,
shadowColor: '#1f2328',
shadowOffset: {
width: 0,
height: 4,
},
shadowOpacity: 0.08,
shadowRadius: 12,
elevation: 4,
},
workingButton: {
backgroundColor: '#ffffff',
borderWidth: 1,
borderColor: '#d1d9e0',
},
placeholderButton: {
backgroundColor: '#ffffff',
borderWidth: 1,
borderColor: '#d1d9e0',
},
menuButtonText: {
fontSize: 17,
fontWeight: '600',
textAlign: 'center',
letterSpacing: -0.2,
},
menuButtonSubtitle: {
fontSize: 12,
marginTop: 6,
textAlign: 'center',
lineHeight: 18,
opacity: 0.9,
},
workingButtonText: {
color: '#0d1117',
},
placeholderButtonText: {
color: '#0d1117',
},
placeholderButtonSubtitle: {
color: '#656d76',
},
workingButtonSubtitle: {
color: '#656d76',
},
disabledButton: {
backgroundColor: '#f6f8fa',
borderColor: '#d1d9e0',
opacity: 0.7,
},
disabledButtonText: {
color: '#8b949e',
},
disabledSubtitleText: {
color: '#656d76',
},
});
63 changes: 63 additions & 0 deletions packages/mobile-sdk-demo/src/components/PlaceholderScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.

import React from 'react';
import { StyleSheet, Text, View } from 'react-native';

import ScreenLayout from './ScreenLayout';

type Props = {
title: string;
onBack: () => void;
description: string;
features: string[];
};

export default function PlaceholderScreen({ title, onBack, description, features }: Props) {
return (
<ScreenLayout title={title} onBack={onBack}>
<View style={styles.content}>
<Text style={styles.description}>{description}</Text>

<View style={styles.features}>
<Text style={styles.featureTitle}>Features (Not Implemented):</Text>
{features.map((f, idx) => (
<Text key={`${idx}-${f}`} style={styles.feature}>
• {f}
</Text>
))}
</View>
</View>
</ScreenLayout>
);
}

const styles = StyleSheet.create({
content: {
flex: 1,
justifyContent: 'center',
},
description: {
fontSize: 16,
textAlign: 'center',
marginBottom: 24,
lineHeight: 24,
},
features: {
backgroundColor: '#f5f5f5',
padding: 16,
borderRadius: 8,
marginBottom: 24,
},
featureTitle: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 12,
},
feature: {
fontSize: 14,
marginBottom: 8,
color: '#333',
},
});
37 changes: 37 additions & 0 deletions packages/mobile-sdk-demo/src/components/ScreenLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.

import React from 'react';
import { StyleSheet, View, type ViewStyle } from 'react-native';

import SafeAreaScrollView from './SafeAreaScrollView';
import StandardHeader from './StandardHeader';

type Props = {
title: string;
onBack: () => void;
children: React.ReactNode;
contentStyle?: ViewStyle;
};

export default function ScreenLayout({ title, onBack, children, contentStyle }: Props) {
return (
<SafeAreaScrollView contentContainerStyle={styles.container} backgroundColor="#fafbfc">
<StandardHeader title={title} onBack={onBack} />
<View style={[styles.content, contentStyle]}>{children}</View>
</SafeAreaScrollView>
);
}

const styles = StyleSheet.create({
container: {
flexGrow: 1,
backgroundColor: '#fafbfc',
paddingHorizontal: 24,
paddingVertical: 20,
},
content: {
flex: 1,
},
});
59 changes: 59 additions & 0 deletions packages/mobile-sdk-demo/src/hooks/useDocuments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.

import { useCallback, useEffect, useState } from 'react';

import type { DocumentMetadata, IDDocument } from '@selfxyz/common/dist/esm/src/utils/types.js';
import { getAllDocuments, useSelfClient } from '@selfxyz/mobile-sdk-alpha';

import { updateAfterDelete } from '../lib/catalog';

export type DocumentEntry = {
metadata: DocumentMetadata;
data: IDDocument;
};

export function useDocuments() {
const selfClient = useSelfClient();
const [documents, setDocuments] = useState<DocumentEntry[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [deleting, setDeleting] = useState<string | null>(null);

const refresh = useCallback(async () => {
setLoading(true);
setError(null);
try {
const all = await getAllDocuments(selfClient);
setDocuments(Object.values(all));
} catch (err) {
setDocuments([]);
setError(err instanceof Error ? err.message : String(err));
} finally {
setLoading(false);
}
}, [selfClient]);

useEffect(() => {
refresh();
}, [refresh]);

const deleteDocument = useCallback(
async (documentId: string) => {
setDeleting(documentId);
try {
await selfClient.deleteDocument(documentId);
const currentCatalog = await selfClient.loadDocumentCatalog();
const updatedCatalog = updateAfterDelete(currentCatalog, documentId);
await selfClient.saveDocumentCatalog(updatedCatalog);
await refresh();
} finally {
setDeleting(null);
}
},
[selfClient, refresh],
);

return { documents, loading, error, deleting, refresh, deleteDocument } as const;
}
Loading
Loading