diff options
| author | MohamedBassem <me@mbassem.com> | 2024-03-13 21:43:44 +0000 |
|---|---|---|
| committer | Mohamed Bassem <me@mbassem.com> | 2024-03-14 16:40:45 +0000 |
| commit | 04572a8e5081b1e4871e273cde9dbaaa44c52fe0 (patch) | |
| tree | 8e993acb732a50d1306d4d6953df96c165c57f57 /packages/mobile/app | |
| parent | 2df08ed08c065e8b91bc8df0266bd4bcbb062be4 (diff) | |
| download | karakeep-04572a8e5081b1e4871e273cde9dbaaa44c52fe0.tar.zst | |
structure: Create apps dir and copy tooling dir from t3-turbo repo
Diffstat (limited to 'packages/mobile/app')
| -rw-r--r-- | packages/mobile/app/+not-found.tsx | 6 | ||||
| -rw-r--r-- | packages/mobile/app/_layout.tsx | 53 | ||||
| -rw-r--r-- | packages/mobile/app/dashboard/(tabs)/_layout.tsx | 38 | ||||
| -rw-r--r-- | packages/mobile/app/dashboard/(tabs)/index.tsx | 31 | ||||
| -rw-r--r-- | packages/mobile/app/dashboard/(tabs)/lists.tsx | 67 | ||||
| -rw-r--r-- | packages/mobile/app/dashboard/(tabs)/search.tsx | 35 | ||||
| -rw-r--r-- | packages/mobile/app/dashboard/(tabs)/settings.tsx | 41 | ||||
| -rw-r--r-- | packages/mobile/app/dashboard/_layout.tsx | 38 | ||||
| -rw-r--r-- | packages/mobile/app/dashboard/add-link.tsx | 57 | ||||
| -rw-r--r-- | packages/mobile/app/dashboard/add-note.tsx | 53 | ||||
| -rw-r--r-- | packages/mobile/app/dashboard/archive.tsx | 11 | ||||
| -rw-r--r-- | packages/mobile/app/dashboard/favourites.tsx | 11 | ||||
| -rw-r--r-- | packages/mobile/app/dashboard/lists/[slug].tsx | 31 | ||||
| -rw-r--r-- | packages/mobile/app/error.tsx | 9 | ||||
| -rw-r--r-- | packages/mobile/app/index.tsx | 20 | ||||
| -rw-r--r-- | packages/mobile/app/sharing.tsx | 99 | ||||
| -rw-r--r-- | packages/mobile/app/signin.tsx | 101 |
17 files changed, 0 insertions, 701 deletions
diff --git a/packages/mobile/app/+not-found.tsx b/packages/mobile/app/+not-found.tsx deleted file mode 100644 index 466505b6..00000000 --- a/packages/mobile/app/+not-found.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { View } from "react-native"; - -// This is kinda important given that the sharing modal always resolve to an unknown route -export default function NotFound() { - return <View />; -} diff --git a/packages/mobile/app/_layout.tsx b/packages/mobile/app/_layout.tsx deleted file mode 100644 index 6304ced5..00000000 --- a/packages/mobile/app/_layout.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import "@/globals.css"; -import "expo-dev-client"; - -import { useRouter } from "expo-router"; -import { Stack } from "expo-router/stack"; -import { useShareIntent } from "expo-share-intent"; -import { StatusBar } from "expo-status-bar"; -import { useEffect } from "react"; -import { View } from "react-native"; - -import { useLastSharedIntent } from "@/lib/last-shared-intent"; -import { Providers } from "@/lib/providers"; - -export default function RootLayout() { - const router = useRouter(); - const { hasShareIntent, shareIntent, resetShareIntent } = useShareIntent(); - - const lastSharedIntent = useLastSharedIntent(); - - useEffect(() => { - const intentJson = JSON.stringify(shareIntent); - if (hasShareIntent && !lastSharedIntent.isPreviouslyShared(intentJson)) { - // TODO: Remove once https://github.com/achorein/expo-share-intent/issues/14 is fixed - lastSharedIntent.setIntent(intentJson); - router.replace({ - pathname: "sharing", - params: { shareIntent: intentJson }, - }); - resetShareIntent(); - } - }, [hasShareIntent]); - - return ( - <Providers> - <View className="h-full w-full bg-white"> - <Stack - screenOptions={{ - headerShown: false, - }} - > - <Stack.Screen name="index" /> - <Stack.Screen - name="sharing" - options={{ - presentation: "modal", - }} - /> - </Stack> - <StatusBar style="auto" /> - </View> - </Providers> - ); -} diff --git a/packages/mobile/app/dashboard/(tabs)/_layout.tsx b/packages/mobile/app/dashboard/(tabs)/_layout.tsx deleted file mode 100644 index 5b2d810a..00000000 --- a/packages/mobile/app/dashboard/(tabs)/_layout.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Tabs } from "expo-router"; -import { ClipboardList, Home, Search, Settings } from "lucide-react-native"; -import React from "react"; - -export default function TabLayout() { - return ( - <Tabs screenOptions={{ tabBarActiveTintColor: "blue" }}> - <Tabs.Screen - name="index" - options={{ - title: "Home", - tabBarIcon: ({ color }) => <Home color={color} />, - }} - /> - <Tabs.Screen - name="search" - options={{ - title: "Search", - tabBarIcon: ({ color }) => <Search color={color} />, - }} - /> - <Tabs.Screen - name="lists" - options={{ - title: "Lists", - tabBarIcon: ({ color }) => <ClipboardList color={color} />, - }} - /> - <Tabs.Screen - name="settings" - options={{ - title: "Settings", - tabBarIcon: ({ color }) => <Settings color={color} />, - }} - /> - </Tabs> - ); -} diff --git a/packages/mobile/app/dashboard/(tabs)/index.tsx b/packages/mobile/app/dashboard/(tabs)/index.tsx deleted file mode 100644 index b2349525..00000000 --- a/packages/mobile/app/dashboard/(tabs)/index.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { Link, Stack } from "expo-router"; -import { SquarePen, Link as LinkIcon } from "lucide-react-native"; -import { View } from "react-native"; - -import BookmarkList from "@/components/bookmarks/BookmarkList"; - -function HeaderRight() { - return ( - <View className="flex flex-row"> - <Link href="dashboard/add-link" className="mt-2 px-2"> - <LinkIcon /> - </Link> - <Link href="dashboard/add-note" className="mt-2 px-2"> - <SquarePen /> - </Link> - </View> - ); -} - -export default function Home() { - return ( - <> - <Stack.Screen - options={{ - headerRight: () => <HeaderRight />, - }} - /> - <BookmarkList archived={false} /> - </> - ); -} diff --git a/packages/mobile/app/dashboard/(tabs)/lists.tsx b/packages/mobile/app/dashboard/(tabs)/lists.tsx deleted file mode 100644 index b534ddda..00000000 --- a/packages/mobile/app/dashboard/(tabs)/lists.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { Link } from "expo-router"; -import { useEffect, useState } from "react"; -import { FlatList, View } from "react-native"; - -import { api } from "@/lib/trpc"; - -export default function Lists() { - const [refreshing, setRefreshing] = useState(false); - const { data: lists, isPending } = api.lists.list.useQuery(); - const apiUtils = api.useUtils(); - - useEffect(() => { - setRefreshing(isPending); - }, [isPending]); - - if (!lists) { - // Add spinner - return <View />; - } - - const onRefresh = () => { - apiUtils.lists.list.invalidate(); - }; - - const links = [ - { - id: "fav", - logo: "⭐️", - name: "Favourites", - href: "/dashboard/favourites", - }, - { - id: "arch", - logo: "🗄️", - name: "Archive", - href: "/dashboard/archive", - }, - ]; - - links.push( - ...lists.lists.map((l) => ({ - id: l.id, - logo: l.icon, - name: l.name, - href: `/dashboard/lists/${l.id}`, - })), - ); - - return ( - <FlatList - contentContainerStyle={{ - gap: 10, - marginTop: 10, - }} - renderItem={(l) => ( - <View className="mx-2 block rounded-xl border border-gray-100 bg-white px-4 py-2"> - <Link key={l.item.id} href={l.item.href} className="text-lg"> - {l.item.logo} {l.item.name} - </Link> - </View> - )} - data={links} - refreshing={refreshing} - onRefresh={onRefresh} - /> - ); -} diff --git a/packages/mobile/app/dashboard/(tabs)/search.tsx b/packages/mobile/app/dashboard/(tabs)/search.tsx deleted file mode 100644 index 980cab36..00000000 --- a/packages/mobile/app/dashboard/(tabs)/search.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { keepPreviousData } from "@tanstack/react-query"; -import { useState } from "react"; -import { View } from "react-native"; -import { useDebounce } from "use-debounce"; - -import BookmarkList from "@/components/bookmarks/BookmarkList"; -import { Divider } from "@/components/ui/Divider"; -import { Input } from "@/components/ui/Input"; -import { api } from "@/lib/trpc"; - -export default function Search() { - const [search, setSearch] = useState(""); - - const [query] = useDebounce(search, 200); - - const { data } = api.bookmarks.searchBookmarks.useQuery( - { text: query }, - { placeholderData: keepPreviousData }, - ); - - return ( - <View> - <Input - placeholder="Search" - className="mx-4 mt-4 bg-white" - value={search} - onChangeText={setSearch} - autoFocus - autoCapitalize="none" - /> - <Divider orientation="horizontal" className="mb-1 mt-4 w-full" /> - {data && <BookmarkList ids={data.bookmarks.map((b) => b.id)} />} - </View> - ); -} diff --git a/packages/mobile/app/dashboard/(tabs)/settings.tsx b/packages/mobile/app/dashboard/(tabs)/settings.tsx deleted file mode 100644 index 9f86d5ec..00000000 --- a/packages/mobile/app/dashboard/(tabs)/settings.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { useRouter } from "expo-router"; -import { useEffect } from "react"; -import { Text, View } from "react-native"; - -import Logo from "@/components/Logo"; -import { Button } from "@/components/ui/Button"; -import { useSession } from "@/lib/session"; -import { api } from "@/lib/trpc"; - -export default function Dashboard() { - const router = useRouter(); - - const { isLoggedIn, logout } = useSession(); - - useEffect(() => { - if (isLoggedIn !== undefined && !isLoggedIn) { - router.replace("signin"); - } - }, [isLoggedIn]); - - const { data, error, isLoading } = api.users.whoami.useQuery(); - - useEffect(() => { - if (error?.data?.code === "UNAUTHORIZED") { - logout(); - } - }, [error]); - - return ( - <View className="flex h-full w-full items-center gap-4 p-4"> - <Logo /> - <View className="w-full rounded-lg bg-white px-4 py-2"> - <Text className="text-lg"> - {isLoading ? "Loading ..." : data?.email} - </Text> - </View> - - <Button className="w-full" label="Log Out" onPress={logout} /> - </View> - ); -} diff --git a/packages/mobile/app/dashboard/_layout.tsx b/packages/mobile/app/dashboard/_layout.tsx deleted file mode 100644 index ff2384d2..00000000 --- a/packages/mobile/app/dashboard/_layout.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Stack } from "expo-router/stack"; - -export default function Dashboard() { - return ( - <Stack> - <Stack.Screen - name="(tabs)" - options={{ headerShown: false, title: "Home" }} - /> - <Stack.Screen - name="favourites" - options={{ - title: "⭐️ Favourites", - }} - /> - <Stack.Screen - name="archive" - options={{ - title: "🗄️ Archive", - }} - /> - <Stack.Screen - name="add-link" - options={{ - title: "New link", - presentation: "modal", - }} - /> - <Stack.Screen - name="add-note" - options={{ - title: "New Note", - presentation: "modal", - }} - /> - </Stack> - ); -} diff --git a/packages/mobile/app/dashboard/add-link.tsx b/packages/mobile/app/dashboard/add-link.tsx deleted file mode 100644 index 69a9c7a2..00000000 --- a/packages/mobile/app/dashboard/add-link.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { useRouter } from "expo-router"; -import { useState } from "react"; -import { View, Text } from "react-native"; - -import { Button } from "@/components/ui/Button"; -import { Input } from "@/components/ui/Input"; -import { api } from "@/lib/trpc"; - -export default function AddNote() { - const [text, setText] = useState(""); - const [error, setError] = useState<string | undefined>(); - const router = useRouter(); - const invalidateAllBookmarks = - api.useUtils().bookmarks.getBookmarks.invalidate; - - const { mutate } = api.bookmarks.createBookmark.useMutation({ - onSuccess: () => { - invalidateAllBookmarks(); - if (router.canGoBack()) { - router.replace("../"); - } else { - router.replace("dashboard"); - } - }, - onError: (e) => { - let message; - if (e.data?.code === "BAD_REQUEST") { - const error = JSON.parse(e.message)[0]; - message = error.message; - } else { - message = `Something went wrong: ${e.message}`; - } - setError(message); - }, - }); - - return ( - <View className="flex gap-2 p-4"> - {error && ( - <Text className="w-full text-center text-red-500">{error}</Text> - )} - <Input - className="bg-white" - value={text} - onChangeText={setText} - placeholder="Link" - autoCapitalize="none" - inputMode="url" - autoFocus - /> - <Button - onPress={() => mutate({ type: "link", url: text })} - label="Add Link" - /> - </View> - ); -} diff --git a/packages/mobile/app/dashboard/add-note.tsx b/packages/mobile/app/dashboard/add-note.tsx deleted file mode 100644 index cf775a15..00000000 --- a/packages/mobile/app/dashboard/add-note.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { useRouter } from "expo-router"; -import { useState } from "react"; -import { View, Text } from "react-native"; - -import { Button } from "@/components/ui/Button"; -import { Input } from "@/components/ui/Input"; -import { api } from "@/lib/trpc"; - -export default function AddNote() { - const [text, setText] = useState(""); - const [error, setError] = useState<string | undefined>(); - const router = useRouter(); - const invalidateAllBookmarks = - api.useUtils().bookmarks.getBookmarks.invalidate; - - const { mutate } = api.bookmarks.createBookmark.useMutation({ - onSuccess: () => { - invalidateAllBookmarks(); - if (router.canGoBack()) { - router.replace("../"); - } else { - router.replace("dashboard"); - } - }, - onError: (e) => { - let message; - if (e.data?.code === "BAD_REQUEST") { - const error = JSON.parse(e.message)[0]; - message = error.message; - } else { - message = `Something went wrong: ${e.message}`; - } - setError(message); - }, - }); - - return ( - <View className="flex gap-2 p-4"> - {error && ( - <Text className="w-full text-center text-red-500">{error}</Text> - )} - <Input - className="bg-white" - value={text} - onChangeText={setText} - multiline - placeholder="What's on your mind?" - autoFocus - /> - <Button onPress={() => mutate({ type: "text", text })} label="Add Note" /> - </View> - ); -} diff --git a/packages/mobile/app/dashboard/archive.tsx b/packages/mobile/app/dashboard/archive.tsx deleted file mode 100644 index d75cfe22..00000000 --- a/packages/mobile/app/dashboard/archive.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { View } from "react-native"; - -import BookmarkList from "@/components/bookmarks/BookmarkList"; - -export default function Archive() { - return ( - <View> - <BookmarkList archived /> - </View> - ); -} diff --git a/packages/mobile/app/dashboard/favourites.tsx b/packages/mobile/app/dashboard/favourites.tsx deleted file mode 100644 index 90374f18..00000000 --- a/packages/mobile/app/dashboard/favourites.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { View } from "react-native"; - -import BookmarkList from "@/components/bookmarks/BookmarkList"; - -export default function Favourites() { - return ( - <View> - <BookmarkList archived={false} favourited /> - </View> - ); -} diff --git a/packages/mobile/app/dashboard/lists/[slug].tsx b/packages/mobile/app/dashboard/lists/[slug].tsx deleted file mode 100644 index 54744874..00000000 --- a/packages/mobile/app/dashboard/lists/[slug].tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { useLocalSearchParams, Stack } from "expo-router"; -import { View } from "react-native"; - -import BookmarkList from "@/components/bookmarks/BookmarkList"; -import FullPageSpinner from "@/components/ui/FullPageSpinner"; -import { api } from "@/lib/trpc"; - -export default function ListView() { - const { slug } = useLocalSearchParams(); - if (typeof slug !== "string") { - throw new Error("Unexpected param type"); - } - const { data: list } = api.lists.get.useQuery({ listId: slug }); - - if (!list) { - return <FullPageSpinner />; - } - - return ( - <> - <Stack.Screen - options={{ - headerTitle: `${list.icon} ${list.name}`, - }} - /> - <View> - <BookmarkList archived={false} ids={list.bookmarks} /> - </View> - </> - ); -} diff --git a/packages/mobile/app/error.tsx b/packages/mobile/app/error.tsx deleted file mode 100644 index 2ca227a4..00000000 --- a/packages/mobile/app/error.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { View, Text } from "react-native"; - -export default function ErrorPage() { - return ( - <View className="flex-1 items-center justify-center gap-4"> - <Text className="text-4xl">Error!</Text> - </View> - ); -} diff --git a/packages/mobile/app/index.tsx b/packages/mobile/app/index.tsx deleted file mode 100644 index 5ce20cda..00000000 --- a/packages/mobile/app/index.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { useRouter } from "expo-router"; -import { useEffect } from "react"; -import { View } from "react-native"; - -import { useSession } from "@/lib/session"; - -export default function App() { - const router = useRouter(); - const { isLoggedIn } = useSession(); - useEffect(() => { - if (isLoggedIn === undefined) { - // Wait until it's loaded - } else if (isLoggedIn) { - router.replace("dashboard"); - } else { - router.replace("signin"); - } - }, [isLoggedIn]); - return <View />; -} diff --git a/packages/mobile/app/sharing.tsx b/packages/mobile/app/sharing.tsx deleted file mode 100644 index 64bbd933..00000000 --- a/packages/mobile/app/sharing.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { Link, useLocalSearchParams, useRouter } from "expo-router"; -import { ShareIntent, useShareIntent } from "expo-share-intent"; -import { useEffect, useMemo, useState } from "react"; -import { View, Text } from "react-native"; -import { z } from "zod"; - -import { api } from "@/lib/trpc"; - -type Mode = - | { type: "idle" } - | { type: "success"; bookmarkId: string } - | { type: "error" }; - -function SaveBookmark({ setMode }: { setMode: (mode: Mode) => void }) { - // Desperate attempt to fix sharing duplication - const { hasShareIntent, resetShareIntent } = useShareIntent(); - - const params = useLocalSearchParams(); - - const shareIntent = useMemo(() => { - if (params && params.shareIntent) { - if (typeof params.shareIntent === "string") { - return JSON.parse(params.shareIntent) as ShareIntent; - } - } - return null; - }, [params]); - - const invalidateAllBookmarks = - api.useUtils().bookmarks.getBookmarks.invalidate; - - useEffect(() => { - if (!isPending && shareIntent?.text) { - const val = z.string().url(); - if (val.safeParse(shareIntent.text).success) { - // This is a URL, else treated as text - mutate({ type: "link", url: shareIntent.text }); - } else { - mutate({ type: "text", text: shareIntent.text }); - } - } - if (hasShareIntent) { - resetShareIntent(); - } - }, []); - - const { mutate, isPending } = api.bookmarks.createBookmark.useMutation({ - onSuccess: (d) => { - invalidateAllBookmarks(); - setMode({ type: "success", bookmarkId: d.id }); - }, - onError: () => { - setMode({ type: "error" }); - }, - }); - - return <Text className="text-4xl">Hoarding ...</Text>; -} - -export default function Sharing() { - const router = useRouter(); - const [mode, setMode] = useState<Mode>({ type: "idle" }); - - let comp; - switch (mode.type) { - case "idle": { - comp = <SaveBookmark setMode={setMode} />; - break; - } - case "success": { - comp = <Text className="text-4xl">Hoarded!</Text>; - break; - } - case "error": { - comp = <Text className="text-4xl">Error!</Text>; - break; - } - } - - // Auto dismiss the modal after saving. - useEffect(() => { - if (mode.type === "idle") { - return; - } - - const timeoutId = setTimeout(() => { - router.replace("dashboard"); - }, 2000); - - return () => clearTimeout(timeoutId); - }, [mode.type]); - - return ( - <View className="flex-1 items-center justify-center gap-4"> - {comp} - <Link href="dashboard">Dismiss</Link> - </View> - ); -} diff --git a/packages/mobile/app/signin.tsx b/packages/mobile/app/signin.tsx deleted file mode 100644 index a89b0087..00000000 --- a/packages/mobile/app/signin.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { useRouter } from "expo-router"; -import { useEffect, useState } from "react"; -import { View, Text } from "react-native"; - -import Logo from "@/components/Logo"; -import { Button } from "@/components/ui/Button"; -import { Input } from "@/components/ui/Input"; -import useAppSettings from "@/lib/settings"; -import { api } from "@/lib/trpc"; - -export default function Signin() { - const router = useRouter(); - - const { settings, setSettings } = useAppSettings(); - - const [error, setError] = useState<string | undefined>(); - - const { mutate: login, isPending } = api.apiKeys.exchange.useMutation({ - onSuccess: (resp) => { - setSettings({ ...settings, apiKey: resp.key }); - router.replace("dashboard"); - }, - onError: (e) => { - if (e.data?.code === "UNAUTHORIZED") { - setError("Wrong username or password"); - } else { - setError(`${e.message}`); - } - }, - }); - - const [formData, setFormData] = useState<{ - email: string; - password: string; - }>({ - email: "", - password: "", - }); - - useEffect(() => { - if (settings.apiKey) { - router.navigate("dashboard"); - } - }, [settings]); - - const onSignin = () => { - const randStr = (Math.random() + 1).toString(36).substring(5); - login({ ...formData, keyName: `Mobile App: (${randStr})` }); - }; - - return ( - <View className="flex h-full flex-col justify-center gap-2 px-4"> - <View className="items-center"> - <Logo /> - </View> - {error && ( - <Text className="w-full text-center text-red-500">{error}</Text> - )} - <View className="gap-2"> - <Text className="font-bold">Server Address</Text> - <Input - className="w-full" - placeholder="Server Address" - value={settings.address} - autoCapitalize="none" - keyboardType="url" - onEndEditing={(e) => - setSettings({ ...settings, address: e.nativeEvent.text }) - } - /> - </View> - <View className="gap-2"> - <Text className="font-bold">Email</Text> - <Input - className="w-full" - placeholder="Email" - keyboardType="email-address" - autoCapitalize="none" - value={formData.email} - onChangeText={(e) => setFormData((s) => ({ ...s, email: e }))} - /> - </View> - <View className="gap-2"> - <Text className="font-bold">Password</Text> - <Input - className="w-full" - placeholder="Password" - secureTextEntry - value={formData.password} - onChangeText={(e) => setFormData((s) => ({ ...s, password: e }))} - /> - </View> - <Button - className="w-full" - label="Sign In" - onPress={onSignin} - disabled={isPending} - /> - </View> - ); -} |
