mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-07-14 18:15:56 -04:00
remove zeego
This commit is contained in:
@@ -8,6 +8,7 @@ node_modules
|
||||
# Build outputs
|
||||
dist
|
||||
build
|
||||
.build
|
||||
*.tsbuildinfo
|
||||
.tanstack
|
||||
|
||||
|
||||
+1
-18
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"plugins": ["oxc", "eslint", "typescript", "react", "import", "unicorn", "vitest", "jsx-a11y"],
|
||||
"jsPlugins": ["@e18e/eslint-plugin"],
|
||||
"env": {
|
||||
"builtin": true
|
||||
},
|
||||
@@ -26,23 +25,7 @@
|
||||
"no-await-in-loop": "off",
|
||||
"react/no-array-index-key": "off",
|
||||
"react/jsx-no-useless-fragment": "error",
|
||||
"react/rules-of-hooks": "error",
|
||||
"e18e/prefer-array-at": "error",
|
||||
"e18e/prefer-array-fill": "error",
|
||||
"e18e/prefer-array-from-map": "error",
|
||||
"e18e/prefer-array-some": "error",
|
||||
"e18e/prefer-array-to-reversed": "error",
|
||||
"e18e/prefer-array-to-sorted": "error",
|
||||
"e18e/prefer-array-to-spliced": "error",
|
||||
"e18e/prefer-date-now": "error",
|
||||
"e18e/prefer-includes": "error",
|
||||
"e18e/prefer-nullish-coalescing": "error",
|
||||
"e18e/prefer-object-has-own": "error",
|
||||
"e18e/prefer-regex-test": "error",
|
||||
"e18e/prefer-spread-syntax": "off",
|
||||
"e18e/prefer-static-regex": "error",
|
||||
"e18e/prefer-timer-args": "error",
|
||||
"e18e/prefer-url-canparse": "error"
|
||||
"react/rules-of-hooks": "error"
|
||||
},
|
||||
"ignorePatterns": ["node_modules", "dist", "build", "docs", "**/routeTree.gen.ts"]
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@better-auth/expo": "catalog:",
|
||||
"@expo/material-symbols": "0.1.1",
|
||||
"@expo/metro-runtime": "~57.0.3",
|
||||
"@expo/ui": "~57.0.4",
|
||||
"@formatjs/intl-datetimeformat": "7.4.9",
|
||||
@@ -30,7 +31,6 @@
|
||||
"@orpc/client": "catalog:",
|
||||
"@orpc/contract": "catalog:",
|
||||
"@orpc/tanstack-query": "catalog:",
|
||||
"@react-native-menu/menu": "2.0.0",
|
||||
"@sentry/react-native": "~8.18.0",
|
||||
"@shopify/flash-list": "2.0.2",
|
||||
"@sofa/api": "workspace:*",
|
||||
@@ -70,11 +70,9 @@
|
||||
"react-dom": "catalog:",
|
||||
"react-native": "0.86.0",
|
||||
"react-native-gesture-handler": "~2.32.0",
|
||||
"react-native-ios-context-menu": "3.2.0",
|
||||
"react-native-ios-utilities": "5.2.0",
|
||||
"react-native-keyboard-controller": "1.21.9",
|
||||
"react-native-mmkv": "4.3.2",
|
||||
"react-native-nitro-modules": "0.36.1",
|
||||
"react-native-mmkv": "~4.3.2",
|
||||
"react-native-nitro-modules": "~0.36.1",
|
||||
"react-native-reanimated": "4.5.0",
|
||||
"react-native-safe-area-context": "~5.7.0",
|
||||
"react-native-screens": "4.25.2",
|
||||
@@ -83,7 +81,6 @@
|
||||
"tailwind-merge": "catalog:",
|
||||
"tailwindcss": "catalog:",
|
||||
"uniwind": "1.10.0",
|
||||
"zeego": "3.0.6",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Icon } from "@expo/ui";
|
||||
import { MenuView } from "@expo/ui/community/menu";
|
||||
import { plural } from "@lingui/core/macro";
|
||||
import { Trans, useLingui } from "@lingui/react/macro";
|
||||
import {
|
||||
@@ -38,7 +40,6 @@ import {
|
||||
} from "react-native";
|
||||
import Animated, { FadeInDown } from "react-native-reanimated";
|
||||
import { useCSSVariable } from "uniwind";
|
||||
import * as DropdownMenu from "zeego/dropdown-menu";
|
||||
|
||||
import { IntegrationsSection } from "@/components/settings/integrations-section";
|
||||
import { SettingsRow } from "@/components/settings/settings-row";
|
||||
@@ -66,6 +67,15 @@ const settingsContentContainerStyle = {
|
||||
paddingHorizontal: 16,
|
||||
};
|
||||
|
||||
const changePhotoIcon = Icon.select({
|
||||
ios: "photo.on.rectangle.angled",
|
||||
android: import("@expo/material-symbols/photo_library.xml"),
|
||||
});
|
||||
const removePhotoIcon = Icon.select({
|
||||
ios: "trash",
|
||||
android: import("@expo/material-symbols/delete.xml"),
|
||||
});
|
||||
|
||||
export default function SettingsScreen() {
|
||||
const { t, i18n } = useLingui();
|
||||
const { push } = useRouter();
|
||||
@@ -241,52 +251,49 @@ export default function SettingsScreen() {
|
||||
<SettingsSection title={t`Account`} icon={IconUser}>
|
||||
<View className="flex-row items-center py-3.5">
|
||||
{hasAvatarImage ? (
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={t`Edit profile photo`}
|
||||
accessibilityHint={t`Opens options to change or remove your photo`}
|
||||
className="mr-3"
|
||||
hitSlop={8}
|
||||
>
|
||||
<View className="bg-secondary size-11 overflow-hidden rounded-full">
|
||||
{isUploadingAvatar ? (
|
||||
<View className="flex-1 items-center justify-center">
|
||||
<Spinner size="sm" colorClassName="accent-primary" />
|
||||
</View>
|
||||
) : (
|
||||
<Image
|
||||
source={{ uri: session.user.image ?? undefined }}
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
contentFit="cover"
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<View className="bg-primary absolute right-0 bottom-0 size-[18px] items-center justify-center rounded-full">
|
||||
<IconCamera size={10} color={primaryFgColor} />
|
||||
</View>
|
||||
</Pressable>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content>
|
||||
<DropdownMenu.Item key="change" onSelect={() => pickAvatar()}>
|
||||
<DropdownMenu.ItemIcon ios={{ name: "photo.on.rectangle.angled" }} />
|
||||
<DropdownMenu.ItemTitle>
|
||||
<Trans>Change Photo</Trans>
|
||||
</DropdownMenu.ItemTitle>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
key="remove"
|
||||
destructive
|
||||
onSelect={() => removeAvatar.mutate()}
|
||||
>
|
||||
<DropdownMenu.ItemIcon ios={{ name: "trash" }} />
|
||||
<DropdownMenu.ItemTitle>
|
||||
<Trans>Remove Photo</Trans>
|
||||
</DropdownMenu.ItemTitle>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
<MenuView
|
||||
actions={[
|
||||
{ id: "change", title: t`Change Photo`, image: changePhotoIcon },
|
||||
{
|
||||
id: "remove",
|
||||
title: t`Remove Photo`,
|
||||
image: removePhotoIcon,
|
||||
attributes: { destructive: true },
|
||||
},
|
||||
]}
|
||||
onPressAction={({ nativeEvent }) => {
|
||||
if (nativeEvent.event === "change") {
|
||||
pickAvatar();
|
||||
} else if (nativeEvent.event === "remove") {
|
||||
removeAvatar.mutate();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={t`Edit profile photo`}
|
||||
accessibilityHint={t`Opens options to change or remove your photo`}
|
||||
className="mr-3"
|
||||
hitSlop={8}
|
||||
>
|
||||
<View className="bg-secondary size-11 overflow-hidden rounded-full">
|
||||
{isUploadingAvatar ? (
|
||||
<View className="flex-1 items-center justify-center">
|
||||
<Spinner size="sm" colorClassName="accent-primary" />
|
||||
</View>
|
||||
) : (
|
||||
<Image
|
||||
source={{ uri: session.user.image ?? undefined }}
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
contentFit="cover"
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<View className="bg-primary absolute right-0 bottom-0 size-[18px] items-center justify-center rounded-full">
|
||||
<IconCamera size={10} color={primaryFgColor} />
|
||||
</View>
|
||||
</Pressable>
|
||||
</MenuView>
|
||||
) : (
|
||||
<Pressable
|
||||
onPress={pickAvatar}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { MenuView } from "@expo/ui/community/menu";
|
||||
import { useLingui } from "@lingui/react/macro";
|
||||
import type { Icon } from "@tabler/icons-react-native";
|
||||
import { Pressable, View } from "react-native";
|
||||
import * as DropdownMenu from "zeego/dropdown-menu";
|
||||
|
||||
import { ScaledIcon } from "@/components/ui/scaled-icon";
|
||||
import { Sparkline } from "@/components/ui/sparkline";
|
||||
@@ -86,29 +86,25 @@ export function StatsCard(props: StatsCardProps) {
|
||||
|
||||
return (
|
||||
<View style={cardStyle}>
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<Pressable
|
||||
onPress={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)}
|
||||
accessibilityRole="button"
|
||||
>
|
||||
<CardInner {...props} />
|
||||
</Pressable>
|
||||
</DropdownMenu.Trigger>
|
||||
|
||||
<DropdownMenu.Content>
|
||||
{periods.map((p) => (
|
||||
<DropdownMenu.CheckboxItem
|
||||
key={p}
|
||||
value={p === period ? "on" : "off"}
|
||||
onValueChange={() => onPeriodChange(p)}
|
||||
>
|
||||
<DropdownMenu.ItemIndicator />
|
||||
<DropdownMenu.ItemTitle>{periodLabels[p]}</DropdownMenu.ItemTitle>
|
||||
</DropdownMenu.CheckboxItem>
|
||||
))}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
<MenuView
|
||||
actions={periods.map((value) => ({
|
||||
id: value,
|
||||
title: periodLabels[value],
|
||||
state: value === period ? "on" : "off",
|
||||
}))}
|
||||
onOpenMenu={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)}
|
||||
onPressAction={({ nativeEvent }) => {
|
||||
const nextPeriod = periods.find((value) => value === nativeEvent.event);
|
||||
if (nextPeriod) onPeriodChange(nextPeriod);
|
||||
}}
|
||||
>
|
||||
<Pressable
|
||||
onPress={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)}
|
||||
accessibilityRole="button"
|
||||
>
|
||||
<CardInner {...props} />
|
||||
</Pressable>
|
||||
</MenuView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Icon } from "@expo/ui";
|
||||
import { MenuView } from "@expo/ui/community/menu";
|
||||
import { useLingui } from "@lingui/react/macro";
|
||||
import { useRouter } from "expo-router";
|
||||
import { Alert, Pressable, View } from "react-native";
|
||||
import * as DropdownMenu from "zeego/dropdown-menu";
|
||||
|
||||
import { Image } from "@/components/ui/image";
|
||||
import { Text } from "@/components/ui/text";
|
||||
@@ -9,6 +10,15 @@ import { queryClient } from "@/lib/query-client";
|
||||
import { authClient } from "@/lib/server";
|
||||
import * as Haptics from "@/utils/haptics";
|
||||
|
||||
const settingsIcon = Icon.select({
|
||||
ios: "gear",
|
||||
android: import("@expo/material-symbols/settings.xml"),
|
||||
});
|
||||
const signOutIcon = Icon.select({
|
||||
ios: "rectangle.portrait.and.arrow.right",
|
||||
android: import("@expo/material-symbols/logout.xml"),
|
||||
});
|
||||
|
||||
export function HeaderAvatar() {
|
||||
const { t } = useLingui();
|
||||
const { data: session } = authClient.useSession();
|
||||
@@ -19,66 +29,61 @@ export function HeaderAvatar() {
|
||||
const { user } = session;
|
||||
|
||||
return (
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<Pressable
|
||||
onPress={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={t`User menu`}
|
||||
hitSlop={8}
|
||||
>
|
||||
<View className="size-8 overflow-hidden rounded-full" accessible={false}>
|
||||
{user.image ? (
|
||||
<Image
|
||||
source={{ uri: user.image }}
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
contentFit="cover"
|
||||
accessible={false}
|
||||
/>
|
||||
) : (
|
||||
<View className="bg-primary/[0.08] flex-1 items-center justify-center">
|
||||
<Text
|
||||
maxFontSizeMultiplier={1.0}
|
||||
className="font-display text-primary text-sm font-medium"
|
||||
>
|
||||
{user.name?.charAt(0)?.toUpperCase() ?? "?"}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</Pressable>
|
||||
</DropdownMenu.Trigger>
|
||||
|
||||
<DropdownMenu.Content>
|
||||
<DropdownMenu.Item key="settings" onSelect={() => navigate("/(tabs)/(settings)")}>
|
||||
<DropdownMenu.ItemIcon ios={{ name: "gear" }} androidIconName="ic_menu_preferences" />
|
||||
<DropdownMenu.ItemTitle>{t`Settings`}</DropdownMenu.ItemTitle>
|
||||
</DropdownMenu.Item>
|
||||
|
||||
<DropdownMenu.Item
|
||||
key="sign-out"
|
||||
destructive
|
||||
onSelect={() => {
|
||||
Alert.alert(t`Sign out`, t`Are you sure you want to sign out?`, [
|
||||
{ text: t`Cancel`, style: "cancel" },
|
||||
{
|
||||
text: t`Sign out`,
|
||||
style: "destructive",
|
||||
onPress: () => {
|
||||
authClient.signOut();
|
||||
queryClient.clear();
|
||||
},
|
||||
<MenuView
|
||||
actions={[
|
||||
{ id: "settings", title: t`Settings`, image: settingsIcon },
|
||||
{
|
||||
id: "sign-out",
|
||||
title: t`Sign out`,
|
||||
image: signOutIcon,
|
||||
attributes: { destructive: true },
|
||||
},
|
||||
]}
|
||||
onOpenMenu={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)}
|
||||
onPressAction={({ nativeEvent }) => {
|
||||
if (nativeEvent.event === "settings") {
|
||||
navigate("/(tabs)/(settings)");
|
||||
} else if (nativeEvent.event === "sign-out") {
|
||||
Alert.alert(t`Sign out`, t`Are you sure you want to sign out?`, [
|
||||
{ text: t`Cancel`, style: "cancel" },
|
||||
{
|
||||
text: t`Sign out`,
|
||||
style: "destructive",
|
||||
onPress: () => {
|
||||
authClient.signOut();
|
||||
queryClient.clear();
|
||||
},
|
||||
]);
|
||||
}}
|
||||
>
|
||||
<DropdownMenu.ItemIcon
|
||||
ios={{ name: "rectangle.portrait.and.arrow.right" }}
|
||||
androidIconName="ic_menu_close_clear_cancel"
|
||||
/>
|
||||
<DropdownMenu.ItemTitle>{t`Sign out`}</DropdownMenu.ItemTitle>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
},
|
||||
]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Pressable
|
||||
onPress={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={t`User menu`}
|
||||
hitSlop={8}
|
||||
>
|
||||
<View className="size-8 overflow-hidden rounded-full" accessible={false}>
|
||||
{user.image ? (
|
||||
<Image
|
||||
source={{ uri: user.image }}
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
contentFit="cover"
|
||||
accessible={false}
|
||||
/>
|
||||
) : (
|
||||
<View className="bg-primary/[0.08] flex-1 items-center justify-center">
|
||||
<Text
|
||||
maxFontSizeMultiplier={1.0}
|
||||
className="font-display text-primary text-sm font-medium"
|
||||
>
|
||||
{user.name?.charAt(0)?.toUpperCase() ?? "?"}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</Pressable>
|
||||
</MenuView>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { MenuView } from "@expo/ui/community/menu";
|
||||
import { useLingui } from "@lingui/react/macro";
|
||||
import { IconArrowsSort } from "@tabler/icons-react-native";
|
||||
import { Pressable } from "react-native";
|
||||
import { useCSSVariable } from "uniwind";
|
||||
import * as DropdownMenu from "zeego/dropdown-menu";
|
||||
|
||||
import { ScaledIcon } from "@/components/ui/scaled-icon";
|
||||
import * as Haptics from "@/utils/haptics";
|
||||
@@ -37,33 +37,28 @@ export function SortMenu({ sortBy, sortDirection, onSortChange }: SortMenuProps)
|
||||
];
|
||||
|
||||
return (
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<Pressable
|
||||
onPress={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={t`Sort`}
|
||||
hitSlop={8}
|
||||
>
|
||||
<ScaledIcon icon={IconArrowsSort} size={22} color={foregroundColor} />
|
||||
</Pressable>
|
||||
</DropdownMenu.Trigger>
|
||||
|
||||
<DropdownMenu.Content>
|
||||
{sortOptions.map((option) => {
|
||||
const isSelected = option.sortBy === sortBy && option.sortDirection === sortDirection;
|
||||
return (
|
||||
<DropdownMenu.CheckboxItem
|
||||
key={`${option.sortBy}-${option.sortDirection}`}
|
||||
value={isSelected ? "on" : "off"}
|
||||
onValueChange={() => onSortChange(option.sortBy, option.sortDirection)}
|
||||
>
|
||||
<DropdownMenu.ItemIndicator />
|
||||
<DropdownMenu.ItemTitle>{option.label}</DropdownMenu.ItemTitle>
|
||||
</DropdownMenu.CheckboxItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
<MenuView
|
||||
actions={sortOptions.map((option) => ({
|
||||
id: `${option.sortBy}-${option.sortDirection}`,
|
||||
title: option.label,
|
||||
state: option.sortBy === sortBy && option.sortDirection === sortDirection ? "on" : "off",
|
||||
}))}
|
||||
onOpenMenu={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)}
|
||||
onPressAction={({ nativeEvent }) => {
|
||||
const option = sortOptions.find(
|
||||
(candidate) => `${candidate.sortBy}-${candidate.sortDirection}` === nativeEvent.event,
|
||||
);
|
||||
if (option) onSortChange(option.sortBy, option.sortDirection);
|
||||
}}
|
||||
>
|
||||
<Pressable
|
||||
onPress={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={t`Sort`}
|
||||
hitSlop={8}
|
||||
>
|
||||
<ScaledIcon icon={IconArrowsSort} size={22} color={foregroundColor} />
|
||||
</Pressable>
|
||||
</MenuView>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -62,7 +62,6 @@
|
||||
"sync:providers": "bun scripts/sync-tmdb-providers.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@e18e/eslint-plugin": "0.5.1",
|
||||
"@lingui/cli": "catalog:",
|
||||
"@lingui/conf": "catalog:",
|
||||
"@lingui/format-po": "catalog:",
|
||||
|
||||
Reference in New Issue
Block a user