aboutsummaryrefslogtreecommitdiffstats
path: root/packages/mobile/app
diff options
context:
space:
mode:
authorMohamedBassem <me@mbassem.com>2024-03-13 21:43:44 +0000
committerMohamed Bassem <me@mbassem.com>2024-03-14 16:40:45 +0000
commit04572a8e5081b1e4871e273cde9dbaaa44c52fe0 (patch)
tree8e993acb732a50d1306d4d6953df96c165c57f57 /packages/mobile/app
parent2df08ed08c065e8b91bc8df0266bd4bcbb062be4 (diff)
downloadkarakeep-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.tsx6
-rw-r--r--packages/mobile/app/_layout.tsx53
-rw-r--r--packages/mobile/app/dashboard/(tabs)/_layout.tsx38
-rw-r--r--packages/mobile/app/dashboard/(tabs)/index.tsx31
-rw-r--r--packages/mobile/app/dashboard/(tabs)/lists.tsx67
-rw-r--r--packages/mobile/app/dashboard/(tabs)/search.tsx35
-rw-r--r--packages/mobile/app/dashboard/(tabs)/settings.tsx41
-rw-r--r--packages/mobile/app/dashboard/_layout.tsx38
-rw-r--r--packages/mobile/app/dashboard/add-link.tsx57
-rw-r--r--packages/mobile/app/dashboard/add-note.tsx53
-rw-r--r--packages/mobile/app/dashboard/archive.tsx11
-rw-r--r--packages/mobile/app/dashboard/favourites.tsx11
-rw-r--r--packages/mobile/app/dashboard/lists/[slug].tsx31
-rw-r--r--packages/mobile/app/error.tsx9
-rw-r--r--packages/mobile/app/index.tsx20
-rw-r--r--packages/mobile/app/sharing.tsx99
-rw-r--r--packages/mobile/app/signin.tsx101
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>
- );
-}