Skip to content

Commit

Permalink
feat: habit info page
Browse files Browse the repository at this point in the history
  • Loading branch information
owengretzinger committed Jan 11, 2025
1 parent 6a8ce4d commit 9d45f45
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 10 deletions.
12 changes: 12 additions & 0 deletions src/api/habits/mock-habits.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ export const mockHabits: { id: HabitIdT; data: DbHabitT }[] = [
displayName: 'John Doe',
username: 'john_doe',
lastActivity: new Date('2024-12-01T00:00:00'),
},
['6' as UserIdT]: {
displayName: 'Sarah Wilson',
username: 'sarah_wilson',
lastActivity: new Date('2024-12-15T00:00:00'),
isOwner: true,
},
},
Expand Down Expand Up @@ -161,6 +166,13 @@ export const mockHabitCompletions: Record<HabitIdT, AllCompletionsT> = {
'2024-12-06': { numberOfCompletions: 1 },
},
},
['6' as UserIdT]: {
entries: {
'2024-12-13': { numberOfCompletions: 1 },
'2024-12-14': { numberOfCompletions: 1 },
'2024-12-15': { numberOfCompletions: 1, note: 'Great book tonight!' },
},
},
},
};
export const setMockHabitCompletions = (
Expand Down
7 changes: 7 additions & 0 deletions src/api/users/mock-users.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ export const mockUsers: UserT[] = [
username: 'lorem_ipsum',
createdAt: new Date(),
},
{
id: '6' as UserIdT,
displayName: 'Sarah Wilson',
username: 'sarah_wilson',
createdAt: new Date(),
},
];

export const mockPictures: Record<UserIdT, string> = {
Expand All @@ -39,6 +45,7 @@ export const mockPictures: Record<UserIdT, string> = {
['3' as UserIdT]: 'https://randomuser.me/api/portraits/women/4.jpg',
['4' as UserIdT]: 'https://randomuser.me/api/portraits/men/5.jpg',
['5' as UserIdT]: 'https://randomuser.me/api/portraits/men/6.jpg',
['6' as UserIdT]: 'https://randomuser.me/api/portraits/women/7.jpg',
};

export const mockRelationships: Record<
Expand Down
101 changes: 95 additions & 6 deletions src/app/habits/view-habit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ import {
type HabitT,
type ParticipantWithIdT,
useAllUsersHabitCompletions,
type UserIdT,
type UserT,
} from '@/api';
import { ErrorMessage } from '@/components/error-message';
import { HabitIcon, type habitIcons } from '@/components/habit-icon';
import ModifyHabitEntry from '@/components/modify-habit-entry';
import { DayNavigation } from '@/components/modify-habit-entry/day-navigation';
import { UserImageNameAndUsername } from '@/components/user-card';
import UserCard, { UserImageNameAndUsername } from '@/components/user-card';
import {
Button,
colors,
Expand Down Expand Up @@ -179,6 +180,18 @@ interface HabitHeaderProps {
const HabitHeader = ({ habit }: HabitHeaderProps) => {
const { colorScheme } = useColorScheme();
const modal = useModal();
const participants = Object.values(habit.participants).filter(
(p) => p !== undefined,
);
const isOwner = habit.participants['1' as UserIdT]?.isOwner;

const handleTransferOwnership = (userId: string) => {
alert('TODO: Transfer ownership to ' + userId);
};

const handleKickUser = (userId: string) => {
alert('TODO: Kick user ' + userId);
};

return (
<>
Expand Down Expand Up @@ -213,23 +226,99 @@ const HabitHeader = ({ habit }: HabitHeaderProps) => {
colorScheme === 'dark' ? colors.neutral[800] : colors.white,
}}
>
<View className="flex-1 px-4">
<Header title={habit.title} />
<View className="flex flex-col gap-4">
<ScrollView className="flex-1 px-4">
<View className="flex flex-col gap-6">
<View
className="flex flex-col gap-2 rounded-xl p-4"
style={{
backgroundColor:
colorScheme === 'dark' ? 'transparent' : habit.color.light,
borderColor:
colorScheme === 'dark' ? habit.color.base : 'transparent',
borderWidth: colorScheme === 'dark' ? 1 : 0,
}}
>
<View className="flex flex-row items-center gap-2">
<HabitIcon
icon={habit.icon}
size={24}
color={colorScheme === 'dark' ? colors.white : colors.black}
strokeWidth={2}
/>
<Text className="text-xl font-semibold">{habit.title}</Text>
</View>
{habit.description && (
<Text className="text-base">{habit.description}</Text>
)}
<Text
className="text-sm"
style={{
color:
colorScheme === 'dark'
? colors.stone[400]
: habit.color.text,
}}
>
{habit.settings.allowMultipleCompletions
? 'Multiple completions allowed per day'
: 'One completion per day limit'}
</Text>
<Text
className="text-sm"
style={{
color:
colorScheme === 'dark'
? colors.stone[400]
: habit.color.text,
}}
>
Created{' '}
{new Date(habit.createdAt).toLocaleDateString(undefined, {
year: 'numeric',
month: 'long',
day: 'numeric',
})}
</Text>
</View>

<Button
label="Invite Friends"
variant="outline"
icon={UserPlusIcon}
onPress={() => alert('todo')}
/>

{participants.length >= 2 && (
<View className="flex flex-col">
<Text className="font-medium">
{participants.length} participants:
</Text>
{participants.map((p) => (
<UserCard
key={p.id}

Check failure on line 297 in src/app/habits/view-habit.tsx

View workflow job for this annotation

GitHub Actions / Type Check (tsc)

'p' is possibly 'undefined'.

Check failure on line 297 in src/app/habits/view-habit.tsx

View workflow job for this annotation

GitHub Actions / Type Check (tsc)

'p' is possibly 'undefined'.
data={{
id: p.id,

Check failure on line 299 in src/app/habits/view-habit.tsx

View workflow job for this annotation

GitHub Actions / Type Check (tsc)

'p' is possibly 'undefined'.
displayName: p.displayName,

Check failure on line 300 in src/app/habits/view-habit.tsx

View workflow job for this annotation

GitHub Actions / Type Check (tsc)

'p' is possibly 'undefined'.
username: p.username,

Check failure on line 301 in src/app/habits/view-habit.tsx

View workflow job for this annotation

GitHub Actions / Type Check (tsc)

'p' is possibly 'undefined'.
createdAt: p.lastActivity,

Check failure on line 302 in src/app/habits/view-habit.tsx

View workflow job for this annotation

GitHub Actions / Type Check (tsc)

'p' is possibly 'undefined'.
}}
showOwnerBadge={p.isOwner}

Check failure on line 304 in src/app/habits/view-habit.tsx

View workflow job for this annotation

GitHub Actions / Type Check (tsc)

'p' is possibly 'undefined'.
showManageOptions={isOwner && p.id !== '1'}

Check failure on line 305 in src/app/habits/view-habit.tsx

View workflow job for this annotation

GitHub Actions / Type Check (tsc)

'p' is possibly 'undefined'.
onTransferOwnership={() => handleTransferOwnership(p.id)}

Check failure on line 306 in src/app/habits/view-habit.tsx

View workflow job for this annotation

GitHub Actions / Type Check (tsc)

'p' is possibly 'undefined'.
onKickUser={() => handleKickUser(p.id)}

Check failure on line 307 in src/app/habits/view-habit.tsx

View workflow job for this annotation

GitHub Actions / Type Check (tsc)

'p' is possibly 'undefined'.
onPress={modal.dismiss}
/>
))}
</View>
)}

<Button
label="Leave Habit"
variant="destructive"
icon={Trash2Icon}
onPress={() => alert('todo')}
/>
</View>
</View>
</ScrollView>
</Modal>
</>
);
Expand Down
71 changes: 68 additions & 3 deletions src/components/user-card.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,38 @@
import { Link } from 'expo-router';
import { EllipsisIcon } from 'lucide-react-native';
import { useColorScheme } from 'nativewind';

import { type UserT } from '@/api';
import { Pressable, Text, View } from '@/ui';
import { colors, Pressable, Text, View } from '@/ui';
import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuItemTitle,
DropdownMenuRoot,
DropdownMenuTrigger,
} from '@/ui/dropdown-menu';

import UserPicture from './picture';

export default function UserCard({ data }: { data: UserT }) {
interface UserCardProps {
data: UserT;
showOwnerBadge?: boolean;
showManageOptions?: boolean;
onTransferOwnership?: () => void;
onKickUser?: () => void;
onPress?: () => void;
}

export default function UserCard({
data,
showOwnerBadge,
showManageOptions,
onTransferOwnership,
onKickUser,
onPress,
}: UserCardProps) {
const { colorScheme } = useColorScheme();

return (
<Link
push
Expand All @@ -18,8 +45,46 @@ export default function UserCard({ data }: { data: UserT }) {
}}
asChild
>
<Pressable className="my-1 rounded-3xl border border-stone-200 bg-white px-4 py-[10px] dark:border-stone-700 dark:bg-transparent">
<Pressable
className="my-1 flex-row items-center justify-between rounded-3xl border border-stone-200 bg-white px-4 py-[10px] dark:border-stone-700 dark:bg-transparent"
onPress={() => onPress?.()}
>
<UserImageNameAndUsername data={data} />
{showOwnerBadge && (
<Text className="text-xs text-stone-400 dark:text-stone-500">
Admin
</Text>
)}
{showManageOptions && (
<DropdownMenuRoot>
<DropdownMenuTrigger>
<Pressable>
<EllipsisIcon
size={24}
color={colorScheme === 'dark' ? colors.white : colors.black}
/>
</Pressable>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
key={'transfer'}
onSelect={onTransferOwnership}
destructive
>
<DropdownMenuItemTitle>
Transfer ownership
</DropdownMenuItemTitle>
</DropdownMenuItem>
<DropdownMenuItem
key={'remove'}
onSelect={onKickUser}
destructive
>
<DropdownMenuItemTitle>Remove from habit</DropdownMenuItemTitle>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenuRoot>
)}
</Pressable>
</Link>
);
Expand Down
2 changes: 1 addition & 1 deletion src/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const button = tv({
},
destructive: {
container:
'border border-red-200 bg-white dark:border-red-500 dark:bg-transparent',
'border border-red-200 bg-white dark:border-red-500 dark:bg-red-500/5',
label: 'font-medium text-red-500',
indicator: 'text-red-500',
},
Expand Down

0 comments on commit 9d45f45

Please sign in to comment.