refactor: update component exports to named exports and enhance UI structure

This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-03-30 19:28:28 -03:00
parent 4dc77b7717
commit 661b821f37
33 changed files with 792 additions and 3067 deletions

1
.gitignore vendored
View file

@ -22,3 +22,4 @@ dist-ssr
*.njsproj
*.sln
*.sw?
output.sh

1131
a.sh

File diff suppressed because it is too large Load diff

View file

@ -1,63 +1,33 @@
import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import DateTimePicker from '@react-native-community/datetimepicker';
import { format } from 'date-fns';
export function CalendarDateRangePicker() {
const [dateRange, setDateRange] = useState({ startDate: new Date(), endDate: new Date() });
const [showStartPicker, setShowStartPicker] = useState(false);
const [showEndPicker, setShowEndPicker] = useState(false);
const onChangeStart = (event, selectedDate) => {
setShowStartPicker(false);
if (selectedDate) {
setDateRange(prev => ({ ...prev, startDate: selectedDate }));
}
};
const onChangeEnd = (event, selectedDate) => {
setShowEndPicker(false);
if (selectedDate) {
setDateRange(prev => ({ ...prev, endDate: selectedDate }));
}
};
const [dateRange, setDateRange] = useState({
startDate: new Date(),
endDate: new Date()
});
return (
<View style={styles.container}>
<TouchableOpacity onPress={() => setShowStartPicker(true)} style={styles.button}>
<Text>Start: {dateRange.startDate.toLocaleDateString()}</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => setShowEndPicker(true)} style={styles.button}>
<Text>End: {dateRange.endDate.toLocaleDateString()}</Text>
</TouchableOpacity>
{showStartPicker && (
<DateTimePicker
value={dateRange.startDate}
mode="date"
display="default"
onChange={onChangeStart}
/>
)}
{showEndPicker && (
<DateTimePicker
value={dateRange.endDate}
mode="date"
display="default"
onChange={onChangeEnd}
/>
)}
</View>
<div className="flex items-center gap-2">
<button
className="px-3 py-1 border rounded"
onClick={() => {
const date = new Date(prompt("Enter start date (YYYY-MM-DD)") || dateRange.startDate);
setDateRange(prev => ({ ...prev, startDate: date }));
}}
>
Start: {format(dateRange.startDate, 'MMM dd, yyyy')}
</button>
<span>to</span>
<button
className="px-3 py-1 border rounded"
onClick={() => {
const date = new Date(prompt("Enter end date (YYYY-MM-DD)") || dateRange.endDate);
setDateRange(prev => ({ ...prev, endDate: date }));
}}
>
End: {format(dateRange.endDate, 'MMM dd, yyyy')}
</button>
</div>
);
}
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
justifyContent: 'space-between',
marginVertical: 10,
},
button: {
padding: 10,
backgroundColor: '#f0f0f0',
borderRadius: 5,
},
});

View file

@ -1,36 +1,16 @@
import React from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
export function MainNav() {
return (
<View style={styles.container}>
<TouchableOpacity style={styles.navItem}>
<Text style={styles.navText}>Overview</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.navItem}>
<Text style={styles.navText}>Customers</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.navItem}>
<Text style={styles.navText}>Products</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.navItem}>
<Text style={styles.navText}>Settings</Text>
</TouchableOpacity>
</View>
<nav className="flex space-x-4">
{['Overview', 'Customers', 'Products', 'Settings'].map((item) => (
<button
key={item}
className="px-3 py-2 text-sm font-medium hover:text-primary"
>
{item}
</button>
))}
</nav>
);
}
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
justifyContent: 'space-around',
alignItems: 'center',
paddingVertical: 10,
},
navItem: {
padding: 10,
},
navText: {
fontSize: 16,
},
});

View file

@ -1,60 +1,50 @@
import React from 'react';
import { View, Dimensions, StyleSheet } from 'react-native';
import { BarChart } from 'react-native-chart-kit';
import { Bar } from 'react-chartjs-2';
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend,
} from 'chart.js';
ChartJS.register(
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend
);
const data = {
labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
datasets: [
{
data: [
Math.random() * 100,
Math.random() * 100,
Math.random() * 100,
Math.random() * 100,
Math.random() * 100,
Math.random() * 100,
Math.random() * 100,
Math.random() * 100,
Math.random() * 100,
Math.random() * 100,
Math.random() * 100,
Math.random() * 100
]
}
]
datasets: [{
label: 'Revenue',
data: Array(12).fill(0).map(() => Math.random() * 100),
backgroundColor: 'rgba(0, 119, 255, 0.5)',
}]
};
const options = {
responsive: true,
plugins: {
legend: {
position: 'top' as const,
},
title: {
display: true,
text: 'Monthly Revenue',
},
},
};
export function Overview() {
return (
<View style={styles.container}>
<BarChart
data={data}
width={Dimensions.get("window").width - 40}
height={220}
yAxisLabel="$"
chartConfig={{
backgroundColor: "#ffffff",
backgroundGradientFrom: "#ffffff",
backgroundGradientTo: "#ffffff",
decimalPlaces: 2,
color: (opacity = 1) => `rgba(0, 0, 0, ${opacity})`,
style: {
borderRadius: 16
}
}}
style={{
marginVertical: 8,
borderRadius: 16
}}
/>
</View>
<div className="p-4 border rounded-lg">
<Bar options={options} data={data} />
</div>
);
}
const styles = StyleSheet.create({
container: {
alignItems: 'center',
justifyContent: 'center',
marginTop: 10,
},
});

View file

@ -1,6 +1,4 @@
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { Avatar, ListItem } from 'react-native-elements';
const salesData = [
{ name: "Olivia Martin", email: "olivia.martin@email.com", amount: "+$1,999.00" },
@ -12,20 +10,21 @@ const salesData = [
export function RecentSales() {
return (
<View>
<div className="space-y-4">
{salesData.map((item, index) => (
<ListItem key={index} bottomDivider>
<Avatar
rounded
source={{ uri: `https://i.pravatar.cc/150?u=${item.email}` }}
/>
<ListItem.Content>
<ListItem.Title>{item.name}</ListItem.Title>
<ListItem.Subtitle>{item.email}</ListItem.Subtitle>
</ListItem.Content>
<Text>{item.amount}</Text>
</ListItem>
<div key={index} className="flex items-center justify-between p-2 border-b">
<div className="flex items-center space-x-3">
<div className="w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center">
{item.name[0]}
</div>
<div>
<p className="font-medium">{item.name}</p>
<p className="text-sm text-gray-500">{item.email}</p>
</div>
</div>
<span className="font-medium">{item.amount}</span>
</div>
))}
</View>
</div>
);
}

View file

@ -1,31 +1,21 @@
import React from 'react';
import { View, StyleSheet } from 'react-native';
import { Input } from 'react-native-elements';
export function Search() {
return (
<View style={styles.container}>
<Input
<div className="relative max-w-md">
<input
type="text"
placeholder="Search..."
containerStyle={styles.inputContainer}
inputContainerStyle={styles.inputInnerContainer}
className="w-full pl-8 pr-4 py-2 rounded-full border focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</View>
<svg
className="absolute left-2.5 top-2.5 h-4 w-4 text-gray-400"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
);
}
const styles = StyleSheet.create({
container: {
width: '100%',
maxWidth: 300,
},
inputContainer: {
paddingHorizontal: 0,
},
inputInnerContainer: {
borderBottomWidth: 0,
backgroundColor: '#f0f0f0',
borderRadius: 20,
paddingHorizontal: 15,
},
});

View file

@ -1,6 +1,4 @@
import React, { useState } from 'react';
import { View, Text, TouchableOpacity, Modal, StyleSheet } from 'react-native';
import { Avatar, Button, Input } from 'react-native-elements';
const groups = [
{
@ -24,57 +22,83 @@ export function TeamSwitcher() {
const [selectedTeam, setSelectedTeam] = useState(groups[0].teams[0]);
return (
<View>
<TouchableOpacity onPress={() => setOpen(true)}>
<Avatar
rounded
source={{ uri: `https://avatar.vercel.sh/${selectedTeam.value}.png` }}
/>
<Text>{selectedTeam.label}</Text>
</TouchableOpacity>
<Modal visible={open} animationType="slide">
<View style={styles.modal}>
<Input placeholder="Search team..." />
<div className="relative">
<button
onClick={() => setOpen(true)}
className="flex items-center space-x-2"
>
<div className="w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center">
{selectedTeam.label[0]}
</div>
<span>{selectedTeam.label}</span>
</button>
{open && (
<div className="absolute z-10 mt-2 w-56 bg-white rounded-md shadow-lg">
<div className="p-2">
<input
type="text"
placeholder="Search team..."
className="w-full p-2 border rounded"
/>
</div>
{groups.map((group) => (
<View key={group.label}>
<Text style={styles.groupLabel}>{group.label}</Text>
<div key={group.label} className="py-1">
<p className="px-3 py-1 text-sm font-medium text-gray-500">{group.label}</p>
{group.teams.map((team) => (
<TouchableOpacity
<button
key={team.value}
onPress={() => {
onClick={() => {
setSelectedTeam(team);
setOpen(false);
}}
className="w-full text-left px-3 py-2 hover:bg-gray-100"
>
<Text>{team.label}</Text>
</TouchableOpacity>
{team.label}
</button>
))}
</View>
</div>
))}
<Button title="Create Team" onPress={() => setShowNewTeamDialog(true)} />
<Button title="Close" onPress={() => setOpen(false)} />
</View>
</Modal>
<Modal visible={showNewTeamDialog} animationType="slide">
<View style={styles.modal}>
<Text>Create team</Text>
<Input placeholder="Team name" />
<Button title="Create" onPress={() => setShowNewTeamDialog(false)} />
<Button title="Cancel" onPress={() => setShowNewTeamDialog(false)} />
</View>
</Modal>
</View>
<div className="border-t p-2">
<button
onClick={() => {
setOpen(false);
setShowNewTeamDialog(true);
}}
className="w-full p-2 text-left hover:bg-gray-100"
>
Create Team
</button>
</div>
</div>
)}
{showNewTeamDialog && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center">
<div className="bg-white p-4 rounded-md">
<h3 className="text-lg font-medium mb-4">Create team</h3>
<input
type="text"
placeholder="Team name"
className="w-full p-2 border rounded mb-4"
/>
<div className="flex justify-end space-x-2">
<button
onClick={() => setShowNewTeamDialog(false)}
className="px-4 py-2 border rounded"
>
Cancel
</button>
<button
onClick={() => setShowNewTeamDialog(false)}
className="px-4 py-2 bg-blue-500 text-white rounded"
>
Create
</button>
</div>
</div>
</div>
)}
</div>
);
}
const styles = StyleSheet.create({
modal: {
flex: 1,
justifyContent: 'center',
padding: 20,
},
groupLabel: {
fontWeight: 'bold',
marginTop: 10,
},
});

View file

@ -1,57 +1,33 @@
import React from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { Avatar, Overlay } from 'react-native-elements';
import React, { useState } from 'react';
export function UserNav() {
const [visible, setVisible] = React.useState(false);
const toggleOverlay = () => {
setVisible(!visible);
};
const [open, setOpen] = useState(false);
return (
<View>
<TouchableOpacity onPress={toggleOverlay}>
<Avatar
rounded
source={{ uri: 'https://i.pravatar.cc/150?u=a042581f4e29026704d' }}
/>
</TouchableOpacity>
<Overlay isVisible={visible} onBackdropPress={toggleOverlay}>
<View>
<Text style={styles.overlayText}>shadcn</Text>
<Text style={styles.overlaySubtext}>m@example.com</Text>
<TouchableOpacity style={styles.overlayButton}>
<Text>Profile</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.overlayButton}>
<Text>Billing</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.overlayButton}>
<Text>Settings</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.overlayButton}>
<Text>New Team</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.overlayButton}>
<Text>Log out</Text>
</TouchableOpacity>
</View>
</Overlay>
</View>
<div className="relative">
<button
onClick={() => setOpen(!open)}
className="w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center"
>
U
</button>
{open && (
<div className="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg z-10">
<div className="p-2 border-b">
<p className="font-medium">shadcn</p>
<p className="text-sm text-gray-500">m@example.com</p>
</div>
{['Profile', 'Billing', 'Settings', 'New Team', 'Log out'].map((item) => (
<button
key={item}
className="w-full text-left px-3 py-2 hover:bg-gray-100"
>
{item}
</button>
))}
</div>
)}
</div>
);
}
const styles = StyleSheet.create({
overlayText: {
fontSize: 18,
fontWeight: 'bold',
},
overlaySubtext: {
fontSize: 14,
color: 'gray',
},
overlayButton: {
paddingVertical: 10,
},
});

View file

@ -1,6 +1,4 @@
import React from 'react';
import { View, Text, ScrollView, StyleSheet } from 'react-native';
import { Button, Card } from 'react-native-elements';
import { TeamSwitcher } from './components/TeamSwitcher';
import { MainNav } from './components/MainNav';
import { Search } from './components/Search';
@ -11,71 +9,56 @@ import { RecentSales } from './components/RecentSales';
export default function DashboardScreen() {
return (
<ScrollView style={styles.container}>
<View style={styles.header}>
<TeamSwitcher />
<MainNav />
<View style={styles.rightHeader}>
<Search />
<UserNav />
</View>
</View>
<View style={styles.content}>
<View style={styles.titleRow}>
<Text style={styles.title}>Dashboard</Text>
<View style={styles.actions}>
<div className="min-h-screen bg-gray-50">
<header className="bg-white border-b">
<div className="container flex items-center justify-between h-16 px-4">
<TeamSwitcher />
<MainNav />
<div className="flex items-center space-x-4">
<Search />
<UserNav />
</div>
</div>
</header>
<main className="container p-4 space-y-4">
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold">Dashboard</h1>
<div className="flex items-center space-x-2">
<CalendarDateRangePicker />
<Button title="Download" />
</View>
</View>
<View style={styles.cards}>
<Card>
<Card.Title>Total Revenue</Card.Title>
<Text style={styles.cardValue}>$45,231.89</Text>
<Text style={styles.cardSubtext}>+20.1% from last month</Text>
</Card>
<Card>
<Card.Title>Subscriptions</Card.Title>
<Text style={styles.cardValue}>+2350</Text>
<Text style={styles.cardSubtext}>+180.1% from last month</Text>
</Card>
<Card>
<Card.Title>Sales</Card.Title>
<Text style={styles.cardValue}>+12,234</Text>
<Text style={styles.cardSubtext}>+19% from last month</Text>
</Card>
<Card>
<Card.Title>Active Now</Card.Title>
<Text style={styles.cardValue}>+573</Text>
<Text style={styles.cardSubtext}>+201 since last hour</Text>
</Card>
</View>
<View style={styles.charts}>
<Card>
<Card.Title>Overview</Card.Title>
<button className="px-4 py-2 bg-blue-500 text-white rounded">
Download
</button>
</div>
</div>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
{[
{ title: "Total Revenue", value: "$45,231.89", subtext: "+20.1% from last month" },
{ title: "Subscriptions", value: "+2350", subtext: "+180.1% from last month" },
{ title: "Sales", value: "+12,234", subtext: "+19% from last month" },
{ title: "Active Now", value: "+573", subtext: "+201 since last hour" },
].map((card, index) => (
<div key={index} className="p-6 bg-white border rounded-lg">
<h3 className="text-sm font-medium text-gray-500">{card.title}</h3>
<p className="text-2xl font-bold mt-1">{card.value}</p>
<p className="text-xs text-gray-500 mt-1">{card.subtext}</p>
</div>
))}
</div>
<div className="grid gap-4 md:grid-cols-2">
<div className="p-6 bg-white border rounded-lg">
<h3 className="text-lg font-medium mb-4">Overview</h3>
<Overview />
</Card>
<Card>
<Card.Title>Recent Sales</Card.Title>
<Text>You made 265 sales this month.</Text>
</div>
<div className="p-6 bg-white border rounded-lg space-y-4">
<h3 className="text-lg font-medium">Recent Sales</h3>
<p>You made 265 sales this month.</p>
<RecentSales />
</Card>
</View>
</View>
</ScrollView>
</div>
</div>
</main>
</div>
);
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor:'white' },
header: { flexDirection: 'row', padding: 16, alignItems: 'center' },
rightHeader: { flexDirection: 'row', marginLeft: 'auto' },
content: { padding: 16 },
titleRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 },
title: { fontSize: 24, fontWeight: 'bold' },
actions: { flexDirection: 'row' },
cards: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'space-between' },
charts: { marginTop: 16 },
cardValue: { fontSize: 20, fontWeight: 'bold' },
cardSubtext: { fontSize: 12, color: 'gray' },
});

View file

@ -7,7 +7,12 @@ interface FileItem {
is_dir: boolean;
}
const FileBrowser = ({ path }: { path: string }) => {
interface FileBrowserProps {
path: string;
onFileSelect?: (path: string) => void;
}
export function FileBrowser({ path, onFileSelect }: FileBrowserProps) {
const [files, setFiles] = useState<FileItem[]>([]);
const [loading, setLoading] = useState(false);
@ -34,7 +39,11 @@ const FileBrowser = ({ path }: { path: string }) => {
) : (
<ul className="space-y-1">
{files.map((file) => (
<li key={file.path} className="p-2 hover:bg-gray-100 rounded">
<li
key={file.path}
className={`p-2 hover:bg-gray-100 rounded cursor-pointer ${file.is_dir ? 'font-medium' : ''}`}
onClick={() => onFileSelect && onFileSelect(file.path)}
>
{file.is_dir ? '📁' : '📄'} {file.name}
</li>
))}
@ -42,6 +51,4 @@ const FileBrowser = ({ path }: { path: string }) => {
)}
</div>
);
};
export default FileBrowser;
}

View file

@ -1,6 +1,5 @@
import { invoke } from '@tauri-apps/api/tauri';
import { open } from '@tauri-apps/api/dialog';
import { writeBinaryFile, BaseDirectory } from '@tauri-apps/api/fs';
import { useState } from 'react';
interface FileOperationsProps {
@ -8,7 +7,7 @@ interface FileOperationsProps {
onRefresh: () => void;
}
const FileOperations = ({ currentPath, onRefresh }: FileOperationsProps) => {
export function FileOperations({ currentPath, onRefresh }: FileOperationsProps) {
const [uploadProgress, setUploadProgress] = useState(0);
const handleUpload = async () => {
@ -79,6 +78,4 @@ const FileOperations = ({ currentPath, onRefresh }: FileOperationsProps) => {
)}
</div>
);
};
export default FileOperations;
}

View file

@ -7,7 +7,11 @@ interface FileItem {
is_dir: boolean;
}
const FileTree = ({ onSelect }: { onSelect: (path: string) => void }) => {
interface FileTreeProps {
onSelect: (path: string) => void;
}
export function FileTree({ onSelect }: FileTreeProps) {
const [tree, setTree] = useState<FileItem[]>([]);
const [expanded, setExpanded] = useState<Record<string, boolean>>({});
@ -43,7 +47,7 @@ const FileTree = ({ onSelect }: { onSelect: (path: string) => void }) => {
</div>
{expanded[item.path] && (
<div className="pl-4">
<FileBrowser path={item.path} />
<FileBrowser path={item.path} onSelect={onSelect} />
</div>
)}
</li>
@ -51,6 +55,4 @@ const FileTree = ({ onSelect }: { onSelect: (path: string) => void }) => {
</ul>
</div>
);
};
export default FileTree;
}

View file

@ -1,9 +1,9 @@
import { useState } from 'react';
import FileTree from './components/FileTree';
import FileBrowser from './components/FileBrowser';
import FileOperations from './components/FileOperations';
import { FileTree } from './components/FileTree';
import { FileBrowser } from './components/FileBrowser';
import { FileOperations } from './components/FileOperations';
const DriveScreen = () => {
export function DriveScreen() {
const [currentPath, setCurrentPath] = useState('');
const [refreshKey, setRefreshKey] = useState(0);
@ -20,6 +20,4 @@ const DriveScreen = () => {
</div>
</div>
);
};
export default DriveScreen;
}

View file

@ -1,6 +1,4 @@
import React, { useState } from 'react';
import { View, Text, TouchableOpacity, Modal, FlatList } from 'react-native';
import { StyleSheet } from 'react-native';
interface Account {
email: string;
@ -13,101 +11,51 @@ interface AccountSwitcherProps {
accounts: Account[];
}
function AccountSwitcher({ isCollapsed, accounts }: AccountSwitcherProps) {
export function AccountSwitcher({ isCollapsed, accounts }: AccountSwitcherProps) {
const [selectedAccount, setSelectedAccount] = useState<string>(accounts[0].email);
const [modalVisible, setModalVisible] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const selectedAccountData = accounts.find((account) => account.email === selectedAccount);
const renderItem = ({ item }: { item: Account }) => (
<TouchableOpacity
style={styles.accountItem}
onPress={() => {
setSelectedAccount(item.email);
setModalVisible(false);
}}
>
<View style={styles.iconContainer}>{item.icon}</View>
<Text style={styles.accountEmail}>{item.email}</Text>
</TouchableOpacity>
);
return (
<View>
<TouchableOpacity
style={[styles.selectTrigger, isCollapsed && styles.collapsedTrigger]}
onPress={() => setModalVisible(true)}
<div className="relative">
<button
onClick={() => setIsOpen(!isOpen)}
className={`flex items-center ${isCollapsed ? 'justify-center' : 'space-x-2'}`}
>
{selectedAccountData && (
<>
<View style={styles.iconContainer}>{selectedAccountData.icon}</View>
<div className="w-6 h-6 flex items-center justify-center">
{selectedAccountData.icon}
</div>
{!isCollapsed && (
<Text style={styles.selectedAccountLabel}>{selectedAccountData.label}</Text>
<span>{selectedAccountData.label}</span>
)}
</>
)}
</TouchableOpacity>
</button>
<Modal
animationType="slide"
transparent={true}
visible={modalVisible}
onRequestClose={() => setModalVisible(false)}
>
<View style={styles.modalContainer}>
<View style={styles.modalContent}>
<FlatList
data={accounts}
renderItem={renderItem}
keyExtractor={(item) => item.email}
/>
</View>
</View>
</Modal>
</View>
{isOpen && (
<div className="absolute z-10 mt-2 w-56 bg-white rounded-md shadow-lg">
<div className="p-2">
{accounts.map((account) => (
<button
key={account.email}
onClick={() => {
setSelectedAccount(account.email);
setIsOpen(false);
}}
className="w-full text-left px-3 py-2 hover:bg-gray-100 flex items-center space-x-2"
>
<div className="w-5 h-5 flex items-center justify-center">
{account.icon}
</div>
<span>{account.label}</span>
</button>
))}
</div>
</div>
)}
</div>
);
}
const styles = StyleSheet.create({
selectTrigger: {
flexDirection: 'row',
alignItems: 'center',
padding: 10,
backgroundColor: '#f0f0f0',
borderRadius: 5,
},
collapsedTrigger: {
width: 40,
height: 40,
justifyContent: 'center',
},
iconContainer: {
marginRight: 10,
},
selectedAccountLabel: {
flex: 1,
},
modalContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'rgba(0, 0, 0, 0.5)',
},
modalContent: {
backgroundColor: 'white',
borderRadius: 10,
padding: 20,
width: '80%',
maxHeight: '80%',
},
accountItem: {
flexDirection: 'row',
alignItems: 'center',
padding: 10,
},
accountEmail: {
marginLeft: 10,
},
});
export default AccountSwitcher;

View file

@ -1,80 +1,8 @@
import React, { useState } from 'react';
import { View, Text, TouchableOpacity, TextInput, StyleSheet, ScrollView, Modal } from 'react-native';
import Icon from 'react-native-vector-icons/Feather';
import { Switch } from 'react-native';
import { format } from 'date-fns';
// Simple Avatar component
const Avatar = ({ name }: { name: string }) => (
<View style={styles.avatar}>
<Text style={styles.avatarText}>{name[0]}</Text>
</View>
);
const formatDistanceToNow = (date: Date) => {
const now = new Date();
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
if (diffInSeconds < 60) return 'just now';
if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)} minutes ago`;
if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)} hours ago`;
if (diffInSeconds < 2592000) return `${Math.floor(diffInSeconds / 86400)} days ago`;
if (diffInSeconds < 31536000) return `${Math.floor(diffInSeconds / 2592000)} months ago`;
return `${Math.floor(diffInSeconds / 31536000)} years ago`;
};
// Simple Separator component
const Separator = () => <View style={styles.separator} />;
// Simple Tooltip component (placeholder)
const Tooltip = ({ content, children }: { content: string; children: React.ReactNode }) => (
<View>
{children}
{/* Implement tooltip logic here */}
</View>
);
// Simple Calendar component (placeholder)
const Calendar = () => (
<View>
<Text>Calendar Placeholder</Text>
</View>
);
// Helper function to format date
const formatDate = (date: Date) => {
return date.toLocaleString('en-US', {
weekday: 'short',
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
hour12: true,
});
};
// Helper function to add hours to a date
const addHours = (date: Date, hours: number) => {
const newDate = new Date(date);
newDate.setHours(date.getHours() + hours);
return newDate;
};
// Helper function to add days to a date
const addDays = (date: Date, days: number) => {
const newDate = new Date(date);
newDate.setDate(date.getDate() + days);
return newDate;
};
// Helper function to get next Saturday
const nextSaturday = (date: Date) => {
const newDate = new Date(date);
newDate.setDate(date.getDate() + ((6 - date.getDay() + 7) % 7));
return newDate;
};
// Define Mail type
interface Mail {
id: string;
name: string;
subject: string;
email: string;
@ -88,255 +16,53 @@ interface MailDisplayProps {
export function MailDisplay({ mail }: MailDisplayProps) {
const [isPopoverVisible, setIsPopoverVisible] = useState(false);
const today = new Date();
const renderToolbarButton = (icon: string, label: string, onPress: () => void) => (
<Tooltip content={label}>
<TouchableOpacity style={styles.toolbarButton} onPress={onPress} disabled={!mail}>
<Icon name={icon} size={16} color="#000" />
</TouchableOpacity>
</Tooltip>
);
if (!mail) {
return (
<div className="flex items-center justify-center h-full">
<p>No message selected</p>
</div>
);
}
return (
<View style={styles.container}>
<View style={styles.toolbar}>
<View style={styles.toolbarGroup}>
{renderToolbarButton('archive', 'Archive', () => {})}
{renderToolbarButton('x-square', 'Move to junk', () => {})}
{renderToolbarButton('trash-2', 'Move to trash', () => {})}
<Separator />
<TouchableOpacity
style={styles.toolbarButton}
onPress={() => setIsPopoverVisible(true)}
disabled={!mail}
>
<Icon name="clock" size={16} color="#000" />
</TouchableOpacity>
</View>
<View style={styles.toolbarGroup}>
{renderToolbarButton('reply', 'Reply', () => {})}
{renderToolbarButton('reply-all', 'Reply all', () => {})}
{renderToolbarButton('corner-up-right', 'Forward', () => {})}
</View>
<Separator />
<TouchableOpacity style={styles.toolbarButton} disabled={!mail}>
<Icon name="more-vertical" size={16} color="#000" />
</TouchableOpacity>
</View>
<Separator />
{mail ? (
<ScrollView style={styles.mailContent}>
<View style={styles.mailHeader}>
<View style={styles.mailSender}>
<Avatar name={mail.name} />
<View style={styles.mailSenderInfo}>
<Text style={styles.mailSenderName}>{mail.name}</Text>
<Text style={styles.mailSubject}>{mail.subject}</Text>
<Text style={styles.mailReplyTo}>
<Text style={styles.mailReplyToLabel}>Reply-To:</Text> {mail.email}
</Text>
</View>
</View>
{mail.date && (
<Text style={styles.mailDate}>
{formatDate(new Date(mail.date))}
</Text>
)}
</View>
<Separator />
<Text style={styles.mailBody}>{mail.text}</Text>
<Separator />
<View style={styles.replyForm}>
<TextInput
style={styles.replyInput}
multiline
placeholder={`Reply ${mail.name}...`}
/>
<View style={styles.replyFormFooter}>
<View style={styles.muteThreadContainer}>
<Switch />
<Text style={styles.muteThreadLabel}>Mute this thread</Text>
</View>
<TouchableOpacity style={styles.sendButton}>
<Text style={styles.sendButtonText}>Send</Text>
</TouchableOpacity>
</View>
</View>
</ScrollView>
) : (
<View style={styles.noMailSelected}>
<Text>No message selected</Text>
</View>
)}
<Modal
visible={isPopoverVisible}
transparent
animationType="slide"
onRequestClose={() => setIsPopoverVisible(false)}
>
<View style={styles.modalOverlay}>
<View style={styles.modalContent}>
<Text style={styles.modalTitle}>Snooze until</Text>
<TouchableOpacity style={styles.snoozeOption}>
<Text>Later today</Text>
<Text style={styles.snoozeTime}>
{formatDate(addHours(today, 4))}
</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.snoozeOption}>
<Text>Tomorrow</Text>
<Text style={styles.snoozeTime}>
{formatDate(addDays(today, 1))}
</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.snoozeOption}>
<Text>This weekend</Text>
<Text style={styles.snoozeTime}>
{formatDate(nextSaturday(today))}
</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.snoozeOption}>
<Text>Next week</Text>
<Text style={styles.snoozeTime}>
{formatDate(addDays(today, 7))}
</Text>
</TouchableOpacity>
<Calendar />
</View>
</View>
</Modal>
</View>
<div className="flex flex-col h-full">
<div className="flex items-center justify-between p-4 border-b">
<div className="flex items-center space-x-4">
<div className="w-10 h-10 rounded-full bg-gray-200 flex items-center justify-center">
{mail.name[0]}
</div>
<div>
<h3 className="font-medium">{mail.name}</h3>
<p className="text-sm text-gray-500">{mail.subject}</p>
<p className="text-xs text-gray-500">Reply-To: {mail.email}</p>
</div>
</div>
<span className="text-sm text-gray-500">
{format(new Date(mail.date), 'MMM d, yyyy h:mm a')}
</span>
</div>
<div className="flex-1 p-4 overflow-auto">
<p className="whitespace-pre-line">{mail.text}</p>
</div>
<div className="p-4 border-t">
<textarea
placeholder={`Reply ${mail.name}...`}
className="w-full p-2 border rounded"
rows={4}
/>
<div className="flex justify-between items-center mt-2">
<label className="flex items-center space-x-2">
<input type="checkbox" />
<span className="text-sm">Mute this thread</span>
</label>
<button className="px-4 py-2 bg-blue-500 text-white rounded">
Send
</button>
</div>
</div>
</div>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
toolbar: {
flexDirection: 'row',
justifyContent: 'space-between',
padding: 8,
},
toolbarGroup: {
flexDirection: 'row',
},
toolbarButton: {
padding: 8,
},
mailContent: {
flex: 1,
},
mailHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
padding: 16,
},
mailSender: {
flexDirection: 'row',
},
mailSenderInfo: {
marginLeft: 16,
},
mailSenderName: {
fontWeight: 'bold',
},
mailSubject: {
fontSize: 12,
},
mailReplyTo: {
fontSize: 12,
},
mailReplyToLabel: {
fontWeight: 'bold',
},
mailDate: {
fontSize: 12,
color: '#666',
},
mailBody: {
padding: 16,
fontSize: 14,
},
replyForm: {
padding: 16,
},
replyInput: {
borderWidth: 1,
borderColor: '#ccc',
borderRadius: 4,
padding: 8,
minHeight: 100,
},
replyFormFooter: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginTop: 8,
},
muteThreadContainer: {
flexDirection: 'row',
alignItems: 'center',
},
muteThreadLabel: {
marginLeft: 8,
fontSize: 12,
},
sendButton: {
backgroundColor: '#007AFF',
padding: 8,
borderRadius: 4,
},
sendButtonText: {
color: '#fff',
},
noMailSelected: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
modalOverlay: {
flex: 1,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
justifyContent: 'center',
alignItems: 'center',
},
modalContent: {
backgroundColor: '#fff',
padding: 16,
borderRadius: 8,
width: '80%',
},
modalTitle: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 16,
},
snoozeOption: {
flexDirection: 'row',
justifyContent: 'space-between',
paddingVertical: 8,
},
snoozeTime: {
color: '#666',
},
avatar: {
width: 40,
height: 40,
borderRadius: 20,
backgroundColor: '#ccc',
justifyContent: 'center',
alignItems: 'center',
},
avatarText: {
fontSize: 18,
color: '#fff',
},
separator: {
height: 1,
backgroundColor: '#e0e0e0',
marginVertical: 8,
},
});

View file

@ -1,147 +1,56 @@
import React from "react";
import { View, Text, TouchableOpacity, FlatList, StyleSheet } from "react-native";
import React from 'react';
import { formatDistanceToNow } from 'date-fns';
import { Mail } from "../data";
import { useMail } from "../use-mail";
interface Mail {
id: string;
name: string;
subject: string;
email: string;
date: string;
text: string;
read: boolean;
labels: string[];
}
interface MailListProps {
items: Mail[];
selectedId?: string;
onSelect: (id: string) => void;
}
export function MailList({ items }: MailListProps) {
const [mail, setMail] = useMail();
const formatDistanceToNow = (date: Date) => {
const now = new Date();
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
if (diffInSeconds < 60) return 'just now';
if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)} minutes ago`;
if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)} hours ago`;
if (diffInSeconds < 2592000) return `${Math.floor(diffInSeconds / 86400)} days ago`;
if (diffInSeconds < 31536000) return `${Math.floor(diffInSeconds / 2592000)} months ago`;
return `${Math.floor(diffInSeconds / 31536000)} years ago`;
};
const renderItem = ({ item }: { item: Mail }) => (
<TouchableOpacity
style={[
styles.mailItem,
mail.selected === item.id && styles.selectedMailItem,
]}
onPress={() =>
setMail({
...mail,
selected: item.id,
})
}
>
<View style={styles.mailHeader}>
<View style={styles.nameContainer}>
<Text style={styles.name}>{item.name}</Text>
{!item.read && <View style={styles.unreadDot} />}
</View>
<Text
style={[
styles.date,
mail.selected === item.id ? styles.selectedDate : styles.mutedDate,
]}
>
{formatDistanceToNow(new Date(item.date))}
</Text>
</View>
<Text style={styles.subject}>{item.subject}</Text>
<Text style={styles.preview} numberOfLines={2}>
{item.text.substring(0, 300)}
</Text>
{item.labels.length > 0 && (
<View style={styles.labelContainer}>
{item.labels.map((label) => (
<View key={label} style={styles.label}>
<Text style={styles.labelText}>{label}</Text>
</View>
))}
</View>
)}
</TouchableOpacity>
);
export function MailList({ items, selectedId, onSelect }: MailListProps) {
return (
<FlatList
data={items}
renderItem={renderItem}
keyExtractor={(item) => item.id}
contentContainerStyle={styles.container}
/>
<div className="space-y-2">
{items.map((item) => (
<div
key={item.id}
onClick={() => onSelect(item.id)}
className={`p-4 border rounded cursor-pointer ${selectedId === item.id ? 'bg-gray-100' : ''}`}
>
<div className="flex justify-between items-start">
<div className="flex items-center space-x-3">
<div className={`w-2 h-2 rounded-full ${item.read ? 'bg-transparent' : 'bg-blue-500'}`} />
<h3 className="font-medium">{item.name}</h3>
</div>
<span className="text-sm text-gray-500">
{formatDistanceToNow(new Date(item.date), { addSuffix: true })}
</span>
</div>
<h4 className="font-medium mt-1">{item.subject}</h4>
<p className="text-sm text-gray-500 mt-1 truncate">
{item.text.substring(0, 100)}...
</p>
{item.labels.length > 0 && (
<div className="flex flex-wrap gap-1 mt-2">
{item.labels.map((label) => (
<span key={label} className="px-2 py-1 text-xs bg-gray-200 rounded">
{label}
</span>
))}
</div>
)}
</div>
))}
</div>
);
}
const styles = StyleSheet.create({
container: {
paddingHorizontal: 16,
paddingTop: 16,
},
mailItem: {
borderWidth: 1,
borderColor: '#e0e0e0',
borderRadius: 8,
padding: 12,
marginBottom: 8,
},
selectedMailItem: {
backgroundColor: '#f0f0f0',
},
mailHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 4,
},
nameContainer: {
flexDirection: 'row',
alignItems: 'center',
},
name: {
fontWeight: 'bold',
marginRight: 8,
},
unreadDot: {
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: 'blue',
},
date: {
fontSize: 12,
},
selectedDate: {
color: '#000',
},
mutedDate: {
color: '#666',
},
subject: {
fontWeight: '500',
marginBottom: 4,
},
preview: {
fontSize: 14,
color: '#666',
marginBottom: 8,
},
labelContainer: {
flexDirection: 'row',
flexWrap: 'wrap',
},
label: {
backgroundColor: '#e0e0e0',
borderRadius: 12,
paddingVertical: 4,
paddingHorizontal: 8,
marginRight: 4,
marginBottom: 4,
},
labelText: {
fontSize: 12,
},
});

View file

@ -1,146 +1,72 @@
import React, { useState } from 'react';
import {
View,
Text,
ScrollView,
StyleSheet,
TouchableOpacity,
TextInput,
} from 'react-native';
import { MailDisplay } from './mail-display';
import { AccountSwitcher } from './account-switcher';
import { MailList } from './mail-list';
import { Nav } from './nav';
import { useMail } from '../use-mail';
import { MailDisplay } from './mail-display';
import { mails, accounts } from '../data';
interface MailProps {
accounts: {
label: string;
email: string;
icon: React.ReactNode;
}[];
mails: [];
}
export function Mail({ accounts, mails }: MailProps) {
export function Mail() {
const [isCollapsed, setIsCollapsed] = useState(false);
const [activeTab, setActiveTab] = useState('all');
const [mail] = useMail();
const [selectedMailId, setSelectedMailId] = useState<string | null>(null);
const toggleCollapse = () => setIsCollapsed(!isCollapsed);
const filteredMails = activeTab === 'all'
? mails
: mails.filter(mail => !mail.read);
return (
<View style={styles.container}>
<View style={[styles.sidebar, isCollapsed && styles.collapsedSidebar]}>
<TouchableOpacity onPress={toggleCollapse} style={styles.collapseButton}>
</TouchableOpacity>
{!isCollapsed && (
<>
</>
)}
</View>
<View style={styles.content}>
<View style={styles.header}>
<Text style={styles.title}>Inbox</Text>
<View style={styles.tabs}>
<TouchableOpacity
style={[styles.tab, activeTab === 'all' && styles.activeTab]}
onPress={() => setActiveTab('all')}
>
<Text>All mail</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.tab, activeTab === 'unread' && styles.activeTab]}
onPress={() => setActiveTab('unread')}
>
<Text>Unread</Text>
</TouchableOpacity>
</View>
</View>
<View style={styles.searchContainer}>
<TextInput
style={styles.searchInput}
placeholder="Search"
placeholderTextColor="gray"
<div className="flex h-screen">
<div className={`${isCollapsed ? 'w-16' : 'w-64'} border-r flex flex-col`}>
<div className="p-4 border-b">
<AccountSwitcher isCollapsed={isCollapsed} accounts={accounts} />
</div>
<div className="p-4">
<button onClick={toggleCollapse} className="w-full text-left">
{isCollapsed ? '»' : '« Collapse'}
</button>
</div>
</div>
<div className="flex-1 flex flex-col border-r">
<div className="p-4 border-b">
<div className="flex justify-between items-center">
<h2 className="text-xl font-bold">Inbox</h2>
<div className="flex space-x-2">
<button
onClick={() => setActiveTab('all')}
className={`px-3 py-1 rounded ${activeTab === 'all' ? 'bg-blue-500 text-white' : ''}`}
>
All mail
</button>
<button
onClick={() => setActiveTab('unread')}
className={`px-3 py-1 rounded ${activeTab === 'unread' ? 'bg-blue-500 text-white' : ''}`}
>
Unread
</button>
</div>
</div>
<div className="mt-4">
<input
type="text"
placeholder="Search"
className="w-full p-2 border rounded"
/>
</div>
</div>
<div className="flex-1 overflow-auto">
<MailList
items={filteredMails}
selectedId={selectedMailId}
onSelect={setSelectedMailId}
/>
</View>
<ScrollView>
<MailList
items={activeTab === 'all' ? mails : mails.filter((item) => !item.read)}
/>
</ScrollView>
</View>
<View style={styles.mailDisplay}>
<MailDisplay
mail={mails.find((item) => item.id === mail.selected) || null}
/>
</View>
</View>
</div>
</div>
<div className="flex-1">
<MailDisplay mail={mails.find(mail => mail.id === selectedMailId) || null} />
</div>
</div>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
},
sidebar: {
width: 250,
backgroundColor: '#f0f0f0',
},
collapsedSidebar: {
width: 50,
},
collapseButton: {
padding: 10,
},
content: {
flex: 1,
backgroundColor: '#ffffff',
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 10,
borderBottomWidth: 1,
borderBottomColor: '#e0e0e0',
},
title: {
fontSize: 20,
fontWeight: 'bold',
},
tabs: {
flexDirection: 'row',
},
tab: {
padding: 10,
},
activeTab: {
borderBottomWidth: 2,
borderBottomColor: 'blue',
},
searchContainer: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#f0f0f0',
margin: 10,
borderRadius: 5,
},
searchIcon: {
padding: 10,
},
searchInput: {
flex: 1,
padding: 10,
},
mailDisplay: {
width: '40%',
borderLeftWidth: 1,
borderLeftColor: '#e0e0e0',
},
});

View file

@ -1,102 +1,36 @@
import React from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { LucideIcon } from 'lucide-react-native';
interface NavProps {
isCollapsed: boolean;
links: {
title: string;
label?: string;
icon: LucideIcon;
icon: React.ReactNode;
variant: 'default' | 'ghost';
}[];
}
export function Nav({ links, isCollapsed }: NavProps) {
return (
<View style={[styles.container, isCollapsed && styles.collapsedContainer]}>
<View style={styles.nav}>
{links.map((link, index) => (
<TouchableOpacity
key={index}
style={[
styles.link,
isCollapsed ? styles.collapsedLink : styles.expandedLink,
link.variant === 'default' ? styles.defaultVariant : styles.ghostVariant,
]}
onPress={() => {/* Add navigation logic here */}}
>
<link.icon
style={[
styles.icon,
isCollapsed ? styles.collapsedIcon : styles.expandedIcon,
]}
/>
<div className={`flex ${isCollapsed ? 'flex-col items-center' : 'flex-col'}`}>
{links.map((link, index) => (
<button
key={index}
className={`flex items-center p-2 rounded ${link.variant === 'default' ? 'bg-gray-100' : ''} ${isCollapsed ? 'justify-center' : 'justify-between'}`}
>
<div className="flex items-center">
<span className="w-5 h-5 flex items-center justify-center">
{link.icon}
</span>
{!isCollapsed && (
<>
<Text style={styles.title}>{link.title}</Text>
{link.label && (
<Text style={styles.label}>{link.label}</Text>
)}
</>
<span className="ml-2">{link.title}</span>
)}
</TouchableOpacity>
))}
</View>
</View>
</div>
{!isCollapsed && link.label && (
<span className="text-xs text-gray-500">{link.label}</span>
)}
</button>
))}
</div>
);
}
const styles = StyleSheet.create({
container: {
flexDirection: 'column',
gap: 16,
paddingVertical: 8,
},
collapsedContainer: {
paddingVertical: 8,
},
nav: {
gap: 4,
paddingHorizontal: 8,
},
link: {
flexDirection: 'row',
alignItems: 'center',
padding: 8,
borderRadius: 4,
},
collapsedLink: {
justifyContent: 'center',
width: 36,
height: 36,
},
expandedLink: {
justifyContent: 'flex-start',
},
defaultVariant: {
backgroundColor: '#f3f4f6',
},
ghostVariant: {
backgroundColor: 'transparent',
},
icon: {
width: 16,
height: 16,
},
collapsedIcon: {
marginRight: 0,
},
expandedIcon: {
marginRight: 8,
},
title: {
fontSize: 14,
fontWeight: '500',
},
label: {
fontSize: 12,
marginLeft: 'auto',
color: '#6b7280',
},
});

View file

@ -19,159 +19,8 @@ export const mails = [
read: true,
labels: ["work", "important"],
},
{
id: "3e7c3f6d-bdf5-46ae-8d90-171300f27ae2",
name: "Bob Johnson",
email: "bobjohnson@example.com",
subject: "Weekend Plans",
text: "Any plans for the weekend? I was thinking of going hiking in the nearby mountains. It's been a while since we had some outdoor fun.\n\nIf you're interested, let me know, and we can plan the details. It'll be a great way to unwind and enjoy nature.\n\nLooking forward to your response!\n\nBest, Bob",
date: "2023-04-10T11:45:00",
read: true,
labels: ["personal"],
},
{
id: "61c35085-72d7-42b4-8d62-738f700d4b92",
name: "Emily Davis",
email: "emilydavis@example.com",
subject: "Re: Question about Budget",
text: "I have a question about the budget for the upcoming project. It seems like there's a discrepancy in the allocation of resources.\n\nI've reviewed the budget report and identified a few areas where we might be able to optimize our spending without compromising the project's quality.\n\nI've attached a detailed analysis for your reference. Let's discuss this further in our next meeting.\n\nThanks, Emily",
date: "2023-03-25T13:15:00",
read: false,
labels: ["work", "budget"],
},
{
id: "8f7b5db9-d935-4e42-8e05-1f1d0a3dfb97",
name: "Michael Wilson",
email: "michaelwilson@example.com",
subject: "Important Announcement",
text: "I have an important announcement to make during our team meeting. It pertains to a strategic shift in our approach to the upcoming product launch. We've received valuable feedback from our beta testers, and I believe it's time to make some adjustments to better meet our customers' needs.\n\nThis change is crucial to our success, and I look forward to discussing it with the team. Please be prepared to share your insights during the meeting.\n\nRegards, Michael",
date: "2023-03-10T15:00:00",
read: false,
labels: ["meeting", "work", "important"],
},
{
id: "1f0f2c02-e299-40de-9b1d-86ef9e42126b",
name: "Sarah Brown",
email: "sarahbrown@example.com",
subject: "Re: Feedback on Proposal",
text: "Thank you for your feedback on the proposal. It looks great! I'm pleased to hear that you found it promising. The team worked diligently to address all the key points you raised, and I believe we now have a strong foundation for the project.\n\nI've attached the revised proposal for your review.\n\nPlease let me know if you have any further comments or suggestions. Looking forward to your response.\n\nBest regards, Sarah",
date: "2023-02-15T16:30:00",
read: true,
labels: ["work"],
},
{
id: "17c0a96d-4415-42b1-8b4f-764efab57f66",
name: "David Lee",
email: "davidlee@example.com",
subject: "New Project Idea",
text: "I have an exciting new project idea to discuss with you. It involves expanding our services to target a niche market that has shown considerable growth in recent months.\n\nI've prepared a detailed proposal outlining the potential benefits and the strategy for execution.\n\nThis project has the potential to significantly impact our business positively. Let's set up a meeting to dive into the details and determine if it aligns with our current goals.\n\nBest regards, David",
date: "2023-01-28T17:45:00",
read: false,
labels: ["meeting", "work", "important"],
},
{
id: "2f0130cb-39fc-44c4-bb3c-0a4337edaaab",
name: "Olivia Wilson",
email: "oliviawilson@example.com",
subject: "Vacation Plans",
text: "Let's plan our vacation for next month. What do you think? I've been thinking of visiting a tropical paradise, and I've put together some destination options.\n\nI believe it's time for us to unwind and recharge. Please take a look at the options and let me know your preferences.\n\nWe can start making arrangements to ensure a smooth and enjoyable trip.\n\nExcited to hear your thoughts! Olivia",
date: "2022-12-20T18:30:00",
read: true,
labels: ["personal"],
},
{
id: "de305d54-75b4-431b-adb2-eb6b9e546014",
name: "James Martin",
email: "jamesmartin@example.com",
subject: "Re: Conference Registration",
text: "I've completed the registration for the conference next month. The event promises to be a great networking opportunity, and I'm looking forward to attending the various sessions and connecting with industry experts.\n\nI've also attached the conference schedule for your reference.\n\nIf there are any specific topics or sessions you'd like me to explore, please let me know. It's an exciting event, and I'll make the most of it.\n\nBest regards, James",
date: "2022-11-30T19:15:00",
read: true,
labels: ["work", "conference"],
},
{
id: "7dd90c63-00f6-40f3-bd87-5060a24e8ee7",
name: "Sophia White",
email: "sophiawhite@example.com",
subject: "Team Dinner",
text: "Let's have a team dinner next week to celebrate our success. We've achieved some significant milestones, and it's time to acknowledge our hard work and dedication.\n\nI've made reservations at a lovely restaurant, and I'm sure it'll be an enjoyable evening.\n\nPlease confirm your availability and any dietary preferences. Looking forward to a fun and memorable dinner with the team!\n\nBest, Sophia",
date: "2022-11-05T20:30:00",
read: false,
labels: ["meeting", "work"],
},
{
id: "99a88f78-3eb4-4d87-87b7-7b15a49a0a05",
name: "Daniel Johnson",
email: "danieljohnson@example.com",
subject: "Feedback Request",
text: "I'd like your feedback on the latest project deliverables. We've made significant progress, and I value your input to ensure we're on the right track.\n\nI've attached the deliverables for your review, and I'm particularly interested in any areas where you think we can further enhance the quality or efficiency.\n\nYour feedback is invaluable, and I appreciate your time and expertise. Let's work together to make this project a success.\n\nRegards, Daniel",
date: "2022-10-22T09:30:00",
read: false,
labels: ["work"],
},
{
id: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
name: "Ava Taylor",
email: "avataylor@example.com",
subject: "Re: Meeting Agenda",
text: "Here's the agenda for our meeting next week. I've included all the topics we need to cover, as well as time allocations for each.\n\nIf you have any additional items to discuss or any specific points to address, please let me know, and we can integrate them into the agenda.\n\nIt's essential that our meeting is productive and addresses all relevant matters.\n\nLooking forward to our meeting! Ava",
date: "2022-10-10T10:45:00",
read: true,
labels: ["meeting", "work"],
},
{
id: "c1a0ecb4-2540-49c5-86f8-21e5ce79e4e6",
name: "William Anderson",
email: "williamanderson@example.com",
subject: "Product Launch Update",
text: "The product launch is on track. I'll provide an update during our call. We've made substantial progress in the development and marketing of our new product.\n\nI'm excited to share the latest updates with you during our upcoming call. It's crucial that we coordinate our efforts to ensure a successful launch. Please come prepared with any questions or insights you may have.\n\nLet's make this product launch a resounding success!\n\nBest regards, William",
date: "2022-09-20T12:00:00",
read: false,
labels: ["meeting", "work", "important"],
},
{
id: "ba54eefd-4097-4949-99f2-2a9ae4d1a836",
name: "Mia Harris",
email: "miaharris@example.com",
subject: "Re: Travel Itinerary",
text: "I've received the travel itinerary. It looks great! Thank you for your prompt assistance in arranging the details. I've reviewed the schedule and the accommodations, and everything seems to be in order. I'm looking forward to the trip, and I'm confident it'll be a smooth and enjoyable experience.\n\nIf there are any specific activities or attractions you recommend at our destination, please feel free to share your suggestions.\n\nExcited for the trip! Mia",
date: "2022-09-10T13:15:00",
read: true,
labels: ["personal", "travel"],
},
{
id: "df09b6ed-28bd-4e0c-85a9-9320ec5179aa",
name: "Ethan Clark",
email: "ethanclark@example.com",
subject: "Team Building Event",
text: "Let's plan a team-building event for our department. Team cohesion and morale are vital to our success, and I believe a well-organized team-building event can be incredibly beneficial. I've done some research and have a few ideas for fun and engaging activities.\n\nPlease let me know your thoughts and availability. We want this event to be both enjoyable and productive.\n\nTogether, we'll strengthen our team and boost our performance.\n\nRegards, Ethan",
date: "2022-08-25T15:30:00",
read: false,
labels: ["meeting", "work"],
},
{
id: "d67c1842-7f8b-4b4b-9be1-1b3b1ab4611d",
name: "Chloe Hall",
email: "chloehall@example.com",
subject: "Re: Budget Approval",
text: "The budget has been approved. We can proceed with the project. I'm delighted to inform you that our budget proposal has received the green light from the finance department. This is a significant milestone, and it means we can move forward with the project as planned.\n\nI've attached the finalized budget for your reference. Let's ensure that we stay on track and deliver the project on time and within budget.\n\nIt's an exciting time for us! Chloe",
date: "2022-08-10T16:45:00",
read: true,
labels: ["work", "budget"],
},
{
id: "6c9a7f94-8329-4d70-95d3-51f68c186ae1",
name: "Samuel Turner",
email: "samuelturner@example.com",
subject: "Weekend Hike",
text: "Who's up for a weekend hike in the mountains? I've been craving some outdoor adventure, and a hike in the mountains sounds like the perfect escape. If you're up for the challenge, we can explore some scenic trails and enjoy the beauty of nature.\n\nI've done some research and have a few routes in mind.\n\nLet me know if you're interested, and we can plan the details.\n\nIt's sure to be a memorable experience! Samuel",
date: "2022-07-28T17:30:00",
read: false,
labels: ["personal"],
},
]
export type Mail = (typeof mails)[number]
// More mails...
];
export const accounts = [
{
@ -197,104 +46,7 @@ export const accounts = [
</svg>
),
},
{
label: "Alicia Koch",
email: "alicia@me.com",
icon: (
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<title>iCloud</title>
<path
d="M13.762 4.29a6.51 6.51 0 0 0-5.669 3.332 3.571 3.571 0 0 0-1.558-.36 3.571 3.571 0 0 0-3.516 3A4.918 4.918 0 0 0 0 14.796a4.918 4.918 0 0 0 4.92 4.914 4.93 4.93 0 0 0 .617-.045h14.42c2.305-.272 4.041-2.258 4.043-4.589v-.009a4.594 4.594 0 0 0-3.727-4.508 6.51 6.51 0 0 0-6.511-6.27z"
fill="currentColor"
/>
</svg>
),
},
]
];
export type Account = (typeof accounts)[number]
export const contacts = [
{
name: "Emma Johnson",
email: "emma.johnson@example.com",
},
{
name: "Liam Wilson",
email: "liam.wilson@example.com",
},
{
name: "Olivia Davis",
email: "olivia.davis@example.com",
},
{
name: "Noah Martinez",
email: "noah.martinez@example.com",
},
{
name: "Ava Taylor",
email: "ava.taylor@example.com",
},
{
name: "Lucas Brown",
email: "lucas.brown@example.com",
},
{
name: "Sophia Smith",
email: "sophia.smith@example.com",
},
{
name: "Ethan Wilson",
email: "ethan.wilson@example.com",
},
{
name: "Isabella Jackson",
email: "isabella.jackson@example.com",
},
{
name: "Mia Clark",
email: "mia.clark@example.com",
},
{
name: "Mason Lee",
email: "mason.lee@example.com",
},
{
name: "Layla Harris",
email: "layla.harris@example.com",
},
{
name: "William Anderson",
email: "william.anderson@example.com",
},
{
name: "Ella White",
email: "ella.white@example.com",
},
{
name: "James Thomas",
email: "james.thomas@example.com",
},
{
name: "Harper Lewis",
email: "harper.lewis@example.com",
},
{
name: "Benjamin Moore",
email: "benjamin.moore@example.com",
},
{
name: "Aria Hall",
email: "aria.hall@example.com",
},
{
name: "Henry Turner",
email: "henry.turner@example.com",
},
{
name: "Scarlett Adams",
email: "scarlett.adams@example.com",
},
]
export type Contact = (typeof contacts)[number]
export type Mail = typeof mails[0];
export type Account = typeof accounts[0];

View file

@ -1,52 +1,10 @@
import React from 'react';
import { View, Image, StyleSheet, useColorScheme } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { accounts, mails } from './data';
import { Mail } from './components/mail';
const MailPage = () => {
const [defaultLayout, setDefaultLayout] = React.useState(undefined);
const [defaultCollapsed, setDefaultCollapsed] = React.useState(undefined);
const colorScheme = useColorScheme();
React.useEffect(() => {
const loadStorageData = async () => {
try {
const layoutData = await AsyncStorage.getItem('react-resizable-panels:layout:mail');
const collapsedData = await AsyncStorage.getItem('react-resizable-panels:collapsed');
setDefaultLayout(layoutData ? JSON.parse(layoutData) : undefined);
setDefaultCollapsed(collapsedData ? JSON.parse(collapsedData) : undefined);
} catch (error) {
console.error('Error loading storage data:', error);
}
};
loadStorageData();
}, []);
export function MailPage() {
return (
<View style={styles.container}>
<Mail
accounts={accounts}
mails={mails}
defaultLayout={defaultLayout}
defaultCollapsed={defaultCollapsed}
navCollapsedSize={4}
/>
</View>
<div className="h-screen">
<Mail />
</div>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
image: {
width: '100%',
height: 200,
},
});
export default MailPage;
}

View file

@ -1,43 +1,9 @@
import { useState, useEffect } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { Mail, mails } from './data'; // Update the import path as needed
type Config = {
selected: Mail['id'] | null;
};
const initialConfig: Config = {
selected: mails[0].id,
};
import { useState } from 'react';
export function useMail() {
const [config, setConfig] = useState<Config>(initialConfig);
const [mail, setMail] = useState({
selected: mails[0].id,
});
useEffect(() => {
// Load config from AsyncStorage when component mounts
loadConfig();
}, []);
const loadConfig = async () => {
try {
const storedConfig = await AsyncStorage.getItem('mailConfig');
if (storedConfig) {
setConfig(JSON.parse(storedConfig));
}
} catch (error) {
console.error('Error loading mail config:', error);
}
};
const updateConfig = async (newConfig: Config) => {
try {
await AsyncStorage.setItem('mailConfig', JSON.stringify(newConfig));
setConfig(newConfig);
} catch (error) {
console.error('Error saving mail config:', error);
}
};
return [config, updateConfig] as const;
return [mail, setMail] as const;
}

View file

@ -21,7 +21,7 @@ interface SyncStatus {
type Screen = "Main" | "Status";
function App() {
function Page() {
const [state, setState] = useState({
name: "",
access_key: "",
@ -200,4 +200,4 @@ function App() {
);
}
export default App;
export default Page;

View file

@ -1,6 +1,4 @@
import React, { useState } from 'react';
import { View, Text, FlatList, TouchableOpacity, StyleSheet } from 'react-native';
import { Task } from '../data/schema';
import { labels, priorities, statuses } from '../data/data';
import { DataTableToolbar } from './DataTableToolbar';
@ -15,50 +13,48 @@ export const DataTable: React.FC<DataTableProps> = ({ data }) => {
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(10);
const renderItem = ({ item }: { item: Task }) => {
const renderItem = (item: Task) => {
const label = labels.find(l => l.value === item.label);
const status = statuses.find(s => s.value === item.status);
const priority = priorities.find(p => p.value === item.priority);
return (
<View style={styles.row}>
<Text style={styles.cell}>{item.id}</Text>
<View style={styles.cell}>
<Text style={styles.badge}>{label?.label}</Text>
<Text>{item.title}</Text>
</View>
<View style={styles.cell}>
<Text>{status?.label}</Text>
</View>
<View style={styles.cell}>
<Text>{priority?.label}</Text>
</View>
<TouchableOpacity style={styles.cell}>
</TouchableOpacity>
</View>
<tr key={item.id} className="border-b">
<td className="p-2">{item.id}</td>
<td className="p-2">
<span className="bg-gray-100 rounded-full px-2 py-1 text-xs mr-2">
{label?.label}
</span>
{item.title}
</td>
<td className="p-2">{status?.label}</td>
<td className="p-2">{priority?.label}</td>
<td className="p-2">
<button className="text-blue-500 hover:text-blue-700">Actions</button>
</td>
</tr>
);
};
return (
<View style={styles.container}>
<div className="flex flex-col h-full">
<DataTableToolbar onFilter={setFilteredData} data={data} />
<FlatList
data={filteredData.slice(page * rowsPerPage, (page + 1) * rowsPerPage)}
renderItem={renderItem}
keyExtractor={(item) => item.id}
ListHeaderComponent={() => (
<View style={styles.header}>
<Text style={styles.headerCell}>ID</Text>
<Text style={styles.headerCell}>Title</Text>
<Text style={styles.headerCell}>Status</Text>
<Text style={styles.headerCell}>Priority</Text>
<Text style={styles.headerCell}>Actions</Text>
</View>
)}
/>
<div className="overflow-auto flex-grow">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Title</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Priority</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{filteredData.slice(page * rowsPerPage, (page + 1) * rowsPerPage).map(renderItem)}
</tbody>
</table>
</div>
<DataTablePagination
page={page}
setPage={setPage}
@ -66,40 +62,6 @@ export const DataTable: React.FC<DataTableProps> = ({ data }) => {
setRowsPerPage={setRowsPerPage}
totalItems={filteredData.length}
/>
</View>
</div>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
header: {
flexDirection: 'row',
borderBottomWidth: 1,
borderBottomColor: '#ccc',
paddingVertical: 10,
},
headerCell: {
flex: 1,
fontWeight: 'bold',
},
row: {
flexDirection: 'row',
borderBottomWidth: 1,
borderBottomColor: '#eee',
paddingVertical: 10,
},
cell: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
},
badge: {
backgroundColor: '#eee',
paddingHorizontal: 5,
paddingVertical: 2,
borderRadius: 10,
marginRight: 5,
},
});

View file

@ -1,6 +1,4 @@
import React from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
interface DataTablePaginationProps {
page: number;
@ -20,38 +18,84 @@ export const DataTablePagination: React.FC<DataTablePaginationProps> = ({
const totalPages = Math.ceil(totalItems / rowsPerPage);
return (
<View style={styles.container}>
<Text>{}</Text>
<View style={styles.navigation}>
<TouchableOpacity onPress={() => setPage(0)} disabled={page === 0}>
</TouchableOpacity>
<TouchableOpacity onPress={() => setPage(page - 1)} disabled={page === 0}>
</TouchableOpacity>
<Text>{}</Text>
<TouchableOpacity onPress={() => setPage(page + 1)} disabled={page === totalPages - 1}>
</TouchableOpacity>
<TouchableOpacity onPress={() => setPage(totalPages - 1)} disabled={page === totalPages - 1}>
</TouchableOpacity>
</View>
</View>
<div className="flex items-center justify-between px-6 py-3 border-t border-gray-200">
<div className="flex-1 flex justify-between sm:hidden">
<button
onClick={() => setPage(page - 1)}
disabled={page === 0}
className="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"
>
Previous
</button>
<button
onClick={() => setPage(page + 1)}
disabled={page === totalPages - 1}
className="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"
>
Next
</button>
</div>
<div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
<div>
<p className="text-sm text-gray-700">
Showing <span className="font-medium">{page * rowsPerPage + 1}</span> to{' '}
<span className="font-medium">
{Math.min((page + 1) * rowsPerPage, totalItems)}
</span>{' '}
of <span className="font-medium">{totalItems}</span> results
</p>
</div>
<div>
<select
value={rowsPerPage}
onChange={(e) => setRowsPerPage(Number(e.target.value))}
className="mr-4 border-gray-300 rounded-md shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
>
{[5, 10, 25, 50].map((size) => (
<option key={size} value={size}>
Show {size}
</option>
))}
</select>
<nav className="relative z-0 inline-flex rounded-md shadow-sm -space-x-px" aria-label="Pagination">
<button
onClick={() => setPage(0)}
disabled={page === 0}
className="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 disabled:opacity-50"
>
<span className="sr-only">First</span>
&laquo;
</button>
<button
onClick={() => setPage(page - 1)}
disabled={page === 0}
className="relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 disabled:opacity-50"
>
<span className="sr-only">Previous</span>
&lsaquo;
</button>
<span className="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700">
Page {page + 1} of {totalPages}
</span>
<button
onClick={() => setPage(page + 1)}
disabled={page === totalPages - 1}
className="relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 disabled:opacity-50"
>
<span className="sr-only">Next</span>
&rsaquo;
</button>
<button
onClick={() => setPage(totalPages - 1)}
disabled={page === totalPages - 1}
className="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 disabled:opacity-50"
>
<span className="sr-only">Last</span>
&raquo;
</button>
</nav>
</div>
</div>
</div>
);
};
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 10,
borderTopWidth: 1,
borderTopColor: '#ccc',
},
navigation: {
flexDirection: 'row',
alignItems: 'center',
},
});

View file

@ -1,6 +1,4 @@
import React, { useState } from 'react';
import { View, TextInput, TouchableOpacity, Text, StyleSheet } from 'react-native';
import { Task } from '../data/schema';
import { priorities, statuses } from '../data/data';
@ -29,53 +27,67 @@ export const DataTableToolbar: React.FC<DataTableToolbarProps> = ({ onFilter, da
? filter.filter(f => f !== value)
: [...filter, value];
setFilter(newFilter);
applyFilters();
};
return (
<View style={styles.container}>
<TextInput
style={styles.input}
placeholder="Filter tasks..."
value={searchText}
onChangeText={(text) => {
setSearchText(text);
applyFilters();
}}
/>
<View style={styles.filterContainer}>
<TouchableOpacity onPress={() => {}} style={styles.filterButton}>
<Text>Status</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => {}} style={styles.filterButton}>
<Text>Priority</Text>
</TouchableOpacity>
</View>
</View>
<div className="px-6 py-4 bg-white border-b border-gray-200">
<div className="flex items-center justify-between">
<div className="flex-1 max-w-lg">
<input
type="text"
placeholder="Filter tasks..."
className="block w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
value={searchText}
onChange={(e) => {
setSearchText(e.target.value);
applyFilters();
}}
/>
</div>
<div className="ml-4 flex space-x-4">
<div className="relative">
<button className="inline-flex justify-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Status
</button>
<div className="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-10">
<div className="py-1">
{statuses.map((status) => (
<label key={status.value} className="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<input
type="checkbox"
checked={statusFilter.includes(status.value)}
onChange={() => toggleFilter(statusFilter, status.value, setStatusFilter)}
className="mr-2"
/>
{status.label}
</label>
))}
</div>
</div>
</div>
<div className="relative">
<button className="inline-flex justify-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Priority
</button>
<div className="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-10">
<div className="py-1">
{priorities.map((priority) => (
<label key={priority.value} className="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<input
type="checkbox"
checked={priorityFilter.includes(priority.value)}
onChange={() => toggleFilter(priorityFilter, priority.value, setPriorityFilter)}
className="mr-2"
/>
{priority.label}
</label>
))}
</div>
</div>
</div>
</div>
</div>
</div>
);
};
const styles = StyleSheet.create({
container: {
padding: 10,
},
input: {
borderWidth: 1,
borderColor: '#ccc',
borderRadius: 5,
padding: 10,
marginBottom: 10,
},
filterContainer: {
flexDirection: 'row',
},
filterButton: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#eee',
padding: 5,
borderRadius: 5,
marginRight: 10,
},
});

View file

@ -1,34 +1,15 @@
import React from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
export const UserNav: React.FC = () => {
return (
<TouchableOpacity style={styles.container}>
<View style={styles.userInfo}>
<Text style={styles.userName}>John Doe</Text>
<Text style={styles.userEmail}>john@example.com</Text>
</View>
</TouchableOpacity>
<div className="flex items-center space-x-4">
<div className="flex-shrink-0">
<img className="h-10 w-10 rounded-full" src="https://via.placeholder.com/40" alt="User avatar" />
</div>
<div>
<div className="font-medium text-gray-900">John Doe</div>
<div className="text-sm text-gray-500">john@example.com</div>
</div>
</div>
);
};
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
padding: 10,
},
userInfo: {
marginLeft: 10,
flex: 1,
},
userName: {
fontWeight: 'bold',
},
userEmail: {
color: 'gray',
},
});

View file

@ -1,9 +1,7 @@
import React from 'react';
import { SafeAreaView, Text, View, StyleSheet } from 'react-native';
import { DataTable } from './components/DataTable';
import { UserNav } from './components/UserNav';
// Mock data - replace with actual data fetching logic
const tasks = [
{
id: "TASK-8782",
@ -26,42 +24,21 @@ const tasks = [
label: "bug",
priority: "high"
},
// ... add more tasks here
];
export default function TaskPage() {
return (
<SafeAreaView style={styles.container}>
<View style={styles.header}>
<View>
<Text style={styles.title}>Welcome back!</Text>
<Text style={styles.subtitle}>Here's a list of your tasks for this month!</Text>
</View>
<div className="min-h-screen bg-white">
<div className="px-6 py-4 border-b border-gray-200 flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-gray-900">Welcome back!</h1>
<p className="text-sm text-gray-500">Here's a list of your tasks for this month!</p>
</div>
<UserNav />
</View>
<DataTable data={tasks} />
</SafeAreaView>
</div>
<div className="h-[calc(100vh-80px)]">
<DataTable data={tasks} />
</div>
</div>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 20,
borderBottomWidth: 1,
borderBottomColor: '#eee',
},
title: {
fontSize: 24,
fontWeight: 'bold',
},
subtitle: {
color: 'gray',
},
});

View file

@ -1,5 +1,4 @@
import React from 'react';
import { View, Text, Image, TouchableOpacity, StyleSheet } from 'react-native';
import { Album } from "../data/albums"
interface AlbumArtworkProps {
@ -16,44 +15,22 @@ export function AlbumArtwork({
height,
}: AlbumArtworkProps) {
return (
<View style={styles.container}>
<TouchableOpacity>
<Image
source={{ uri: album.cover }}
style={[
styles.image,
{
width: width,
height: height,
aspectRatio: aspectRatio === "portrait" ? 3/4 : 1,
},
]}
<div className="mr-4">
<div className="overflow-hidden rounded-md">
<img
src={album.cover}
alt={album.name}
className={`object-cover transition-all hover:scale-105 ${aspectRatio === "portrait" ? "aspect-[3/4]" : "aspect-square"}`}
style={{
width: width,
height: height,
}}
/>
</TouchableOpacity>
<View style={styles.textContainer}>
<Text style={styles.albumName}>{album.name}</Text>
<Text style={styles.artistName}>{album.artist}</Text>
</View>
</View>
)
</div>
<div className="mt-2 space-y-1 text-sm">
<h3 className="font-medium leading-none">{album.name}</h3>
<p className="text-xs text-gray-500">{album.artist}</p>
</div>
</div>
);
}
const styles = StyleSheet.create({
container: {
marginRight: 16,
},
image: {
borderRadius: 8,
},
textContainer: {
marginTop: 8,
},
albumName: {
fontSize: 14,
fontWeight: 'bold',
},
artistName: {
fontSize: 12,
color: '#666',
},
});

View file

@ -1,39 +1,13 @@
import React from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
export function Menu() {
return (
<View style={styles.container}>
<TouchableOpacity style={styles.menuItem}>
<Text style={styles.menuText}>Music</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.menuItem}>
<Text style={styles.menuText}>File</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.menuItem}>
<Text style={styles.menuText}>Edit</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.menuItem}>
<Text style={styles.menuText}>View</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.menuItem}>
<Text style={styles.menuText}>Account</Text>
</TouchableOpacity>
</View>
)
<div className="flex justify-around py-2 bg-gray-100">
<button className="px-4 py-2 text-sm font-medium">Music</button>
<button className="px-4 py-2 text-sm font-medium">File</button>
<button className="px-4 py-2 text-sm font-medium">Edit</button>
<button className="px-4 py-2 text-sm font-medium">View</button>
<button className="px-4 py-2 text-sm font-medium">Account</button>
</div>
);
}
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
justifyContent: 'space-around',
paddingVertical: 10,
backgroundColor: '#f0f0f0',
},
menuItem: {
padding: 10,
},
menuText: {
fontSize: 16,
},
});

View file

@ -1,47 +1,17 @@
import React from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
export function PodcastEmptyPlaceholder() {
return (
<View style={styles.container}>
<View style={styles.content}>
<Text style={styles.title}>No episodes added</Text>
<Text style={styles.description}>You have not added any podcasts. Add one below.</Text>
<TouchableOpacity style={styles.button}>
<Text style={styles.buttonText}>Add Podcast</Text>
</TouchableOpacity>
</View>
</View>
)
<div className="flex items-center justify-center h-full p-8">
<div className="text-center">
<h3 className="text-xl font-bold">No episodes added</h3>
<p className="mb-4 text-sm text-gray-500">
You have not added any podcasts. Add one below.
</p>
<button className="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
Add Podcast
</button>
</div>
</div>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
content: {
alignItems: 'center',
},
title: {
fontSize: 20,
fontWeight: 'bold',
marginBottom: 10,
},
description: {
textAlign: 'center',
marginBottom: 20,
},
button: {
backgroundColor: '#007AFF',
paddingHorizontal: 20,
paddingVertical: 10,
borderRadius: 5,
},
buttonText: {
color: 'white',
fontWeight: 'bold',
},
});

View file

@ -1,5 +1,4 @@
import React from 'react';
import { View, Text, ScrollView, TouchableOpacity, StyleSheet } from 'react-native';
import { Playlist } from "../data/playlists"
interface SidebarProps {
@ -8,83 +7,35 @@ interface SidebarProps {
export function Sidebar({ playlists }: SidebarProps) {
return (
<View style={styles.container}>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Discover</Text>
<View style={styles.buttonContainer}>
<TouchableOpacity style={styles.button}>
<Text style={styles.buttonText}>Listen Now</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button}>
<Text style={styles.buttonText}>Browse</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button}>
<Text style={styles.buttonText}>Radio</Text>
</TouchableOpacity>
</View>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Library</Text>
<View style={styles.buttonContainer}>
<TouchableOpacity style={styles.button}>
<Text style={styles.buttonText}>Playlists</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button}>
<Text style={styles.buttonText}>Songs</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button}>
<Text style={styles.buttonText}>Made for You</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button}>
<Text style={styles.buttonText}>Artists</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button}>
<Text style={styles.buttonText}>Albums</Text>
</TouchableOpacity>
</View>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Playlists</Text>
<ScrollView style={styles.playlistContainer}>
<div className="p-3">
<div className="mb-5">
<h2 className="text-lg font-bold mb-2">Discover</h2>
<div className="space-y-1 ml-2">
<button className="block w-full text-left px-2 py-1 text-sm hover:bg-gray-100 rounded">Listen Now</button>
<button className="block w-full text-left px-2 py-1 text-sm hover:bg-gray-100 rounded">Browse</button>
<button className="block w-full text-left px-2 py-1 text-sm hover:bg-gray-100 rounded">Radio</button>
</div>
</div>
<div className="mb-5">
<h2 className="text-lg font-bold mb-2">Library</h2>
<div className="space-y-1 ml-2">
<button className="block w-full text-left px-2 py-1 text-sm hover:bg-gray-100 rounded">Playlists</button>
<button className="block w-full text-left px-2 py-1 text-sm hover:bg-gray-100 rounded">Songs</button>
<button className="block w-full text-left px-2 py-1 text-sm hover:bg-gray-100 rounded">Made for You</button>
<button className="block w-full text-left px-2 py-1 text-sm hover:bg-gray-100 rounded">Artists</button>
<button className="block w-full text-left px-2 py-1 text-sm hover:bg-gray-100 rounded">Albums</button>
</div>
</div>
<div>
<h2 className="text-lg font-bold mb-2">Playlists</h2>
<div className="max-h-72 overflow-y-auto">
{playlists?.map((playlist, i) => (
<TouchableOpacity style={styles.playlistButton}>
<Text style={styles.playlistButtonText}>{playlist}</Text>
</TouchableOpacity>
<button key={i} className="block w-full text-left px-2 py-1 text-sm hover:bg-gray-100 rounded">
{playlist}
</button>
))}
</ScrollView>
</View>
</View>
)
</div>
</div>
</div>
);
}
const styles = StyleSheet.create({
container: {
padding: 12,
},
section: {
marginBottom: 20,
},
sectionTitle: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 10,
},
buttonContainer: {
marginLeft: 10,
},
button: {
paddingVertical: 8,
},
buttonText: {
fontSize: 16,
},
playlistContainer: {
maxHeight: 300,
},
playlistButton: {
paddingVertical: 8,
},
playlistButtonText: {
fontSize: 16,
},
});

View file

@ -1,5 +1,4 @@
import React from 'react';
import { View, Text, ScrollView, StyleSheet, SafeAreaView } from 'react-native';
import { Menu } from './components/menu';
import { Sidebar } from './components/sidebar';
import { PodcastEmptyPlaceholder } from './components/podcast-empty-placeholder';
@ -9,72 +8,45 @@ import { playlists } from './data/playlists';
export default function TemplatesPage() {
return (
<SafeAreaView style={styles.container}>
<div className="min-h-screen bg-white">
<Menu />
<View style={styles.content}>
<Sidebar playlists={playlists} />
<ScrollView style={styles.mainContent}>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Listen Now</Text>
<Text style={styles.sectionSubtitle}>Top picks for you. Updated daily.</Text>
</View>
<ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.albumList}>
{listenNowAlbums.map((album) => (
<AlbumArtwork
key={album.name}
album={album}
width={250}
height={330}
/>
))}
</ScrollView>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Made for You</Text>
<Text style={styles.sectionSubtitle}>Your personal playlists. Updated daily.</Text>
</View>
<ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.albumList}>
{madeForYouAlbums.map((album) => (
<AlbumArtwork
key={album.name}
album={album}
width={150}
height={150}
aspectRatio="square"
/>
))}
</ScrollView>
<div className="flex">
<div className="w-64 border-r border-gray-200">
<Sidebar playlists={playlists} />
</div>
<div className="flex-1 p-4 overflow-auto">
<div className="mb-8">
<h2 className="text-2xl font-bold">Listen Now</h2>
<p className="text-gray-500">Top picks for you. Updated daily.</p>
<div className="flex overflow-x-auto py-4 space-x-4">
{listenNowAlbums.map((album) => (
<AlbumArtwork
key={album.name}
album={album}
width={250}
height={330}
/>
))}
</div>
</div>
<div className="mb-8">
<h2 className="text-2xl font-bold">Made for You</h2>
<p className="text-gray-500">Your personal playlists. Updated daily.</p>
<div className="flex overflow-x-auto py-4 space-x-4">
{madeForYouAlbums.map((album) => (
<AlbumArtwork
key={album.name}
album={album}
width={150}
height={150}
aspectRatio="square"
/>
))}
</div>
</div>
<PodcastEmptyPlaceholder />
</ScrollView>
</View>
</SafeAreaView>
</div>
</div>
</div>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
content: {
flex: 1,
flexDirection: 'row',
},
mainContent: {
flex: 1,
padding: 16,
},
section: {
marginBottom: 16,
},
sectionTitle: {
fontSize: 24,
fontWeight: 'bold',
},
sectionSubtitle: {
fontSize: 16,
color: '#666',
},
albumList: {
marginBottom: 24,
},
});