Your music, beautifully tracked. All yours. (coming soon) teal.fm
teal-fm atproto

cheeky format

Natalie B 18a0d2f9 14b70559

+89 -65
+29 -21
apps/amethyst/app/(tabs)/_layout.tsx
··· 1 - 2 - import React from 'react'; 1 + import React from "react"; 3 2 4 3 import { 5 4 FilePen, ··· 8 7 Search, 9 8 Settings, 10 9 type LucideIcon, 11 - } from 'lucide-react-native'; 12 - import { Link, Tabs } from 'expo-router'; 13 - import { Pressable } from 'react-native'; 10 + } from "lucide-react-native"; 11 + import { Link, Tabs } from "expo-router"; 12 + import { Pressable } from "react-native"; 14 13 15 - import Colors from '../../constants/Colors'; 16 - import { Icon, iconWithClassName } from '../../lib/icons/iconWithClassName'; 14 + import Colors from "../../constants/Colors"; 15 + import { Icon, iconWithClassName } from "../../lib/icons/iconWithClassName"; 17 16 //import useIsMobile from "@/hooks/useIsMobile"; 18 - import { useStore } from '@/stores/mainStore'; 19 - import { useColorScheme } from 'nativewind'; 20 - import AuthOptions from '../auth/options'; 21 - import useIsMobile from '@/hooks/useIsMobile'; 17 + import { useStore } from "@/stores/mainStore"; 18 + import { useColorScheme } from "nativewind"; 19 + import AuthOptions from "../auth/options"; 20 + import useIsMobile from "@/hooks/useIsMobile"; 22 21 23 22 function TabBarIcon(props: { name: LucideIcon; color: string }) { 24 23 const Name = props.name; ··· 31 30 const authStatus = useStore((state) => state.status); 32 31 const isMobile = useIsMobile(); 33 32 // if we are on web but not native and web width is greater than 1024px 34 - const hideTabBar = authStatus !== 'loggedIn'; // || useIsMobile() 33 + const hideTabBar = authStatus !== "loggedIn"; // || useIsMobile() 35 34 36 35 const j = useStore((state) => state.status); 37 36 // @me 38 37 const agent = useStore((state) => state.pdsAgent); 39 - const profile = useStore((state) => state.profiles[agent?.did ?? '']); 38 + const profile = useStore((state) => state.profiles[agent?.did ?? ""]); 40 39 41 - if (j !== 'loggedIn') { 40 + if (j !== "loggedIn") { 42 41 return <AuthOptions />; 43 42 } 44 43 45 44 return ( 46 45 <Tabs 47 46 screenOptions={{ 48 - title: 'Home', 49 - tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint, 47 + title: "Home", 48 + tabBarActiveTintColor: Colors[colorScheme ?? "light"].tint, 50 49 // Disable the static render of the header on web 51 50 // to prevent a hydration error in 52 51 // React Navigation v6. ··· 58 57 59 58 tabBarStyle: { 60 59 //height: 75, 61 - display: hideTabBar ? 'none' : 'flex', 60 + display: hideTabBar ? "none" : "flex", 62 61 }, 63 62 }} 64 63 > 65 64 <Tabs.Screen 66 65 name="index" 67 66 options={{ 68 - title: 'Home', 67 + title: "Home", 69 68 tabBarIcon: ({ color }) => <TabBarIcon name={Home} color={color} />, 70 69 headerRight: () => ( 71 70 <Link href="/auth/logoutModal" asChild> ··· 85 84 <Tabs.Screen 86 85 name="search/index" 87 86 options={{ 88 - title: 'Search', 87 + title: "Search", 89 88 tabBarIcon: ({ color }) => <TabBarIcon name={Search} color={color} />, 90 89 }} 91 90 /> 92 91 <Tabs.Screen 93 92 name="(stamp)" 94 93 options={{ 95 - title: 'Stamp', 94 + title: "Stamp", 96 95 tabBarIcon: ({ color }) => ( 97 96 <TabBarIcon name={FilePen} color={color} /> 98 97 ), ··· 101 100 <Tabs.Screen 102 101 name="settings/index" 103 102 options={{ 104 - title: 'Settings', 103 + title: "Settings", 105 104 106 105 tabBarIcon: ({ color }) => ( 107 106 <TabBarIcon name={Settings} color={color} /> 107 + ), 108 + }} 109 + /> 110 + <Tabs.Screen 111 + name="/profile/undefined" 112 + options={{ 113 + title: "Stamp", 114 + tabBarIcon: ({ color }) => ( 115 + <TabBarIcon name={FilePen} color={color} /> 108 116 ), 109 117 }} 110 118 />
+14 -10
apps/amethyst/app/(tabs)/profile/[handle].tsx
··· 1 - import ActorView from '@/components/actor/actorView'; 2 - import { Text } from '@/components/ui/text'; 3 - import { resolveHandle } from '@/lib/atp/pid'; 4 - import { useStore } from '@/stores/mainStore'; 5 - import { Stack, useLocalSearchParams } from 'expo-router'; 6 - import { useEffect, useState } from 'react'; 7 - import { ActivityIndicator, ScrollView, View } from 'react-native'; 1 + import ActorView from "@/components/actor/actorView"; 2 + import { Text } from "@/components/ui/text"; 3 + import { resolveHandle } from "@/lib/atp/pid"; 4 + import { useStore } from "@/stores/mainStore"; 5 + import { Stack, useLocalSearchParams } from "expo-router"; 6 + import { useEffect, useState } from "react"; 7 + import { ActivityIndicator, ScrollView, View } from "react-native"; 8 8 9 9 export default function Handle() { 10 10 let { handle } = useLocalSearchParams(); ··· 18 18 const agent = await resolveHandle(handle); 19 19 setDid(agent); 20 20 }; 21 - fetchAgent(); 21 + if (handle !== "undefined") fetchAgent(); 22 22 }, [handle]); 23 23 24 + if (handle === "undefined") { 25 + return <Text>Handle is undefined</Text>; 26 + } 27 + 24 28 if (!did) return <ActivityIndicator size="large" color="#0000ff" />; 25 29 26 30 return ( 27 31 <ScrollView className="flex-1 justify-start items-center gap-5 bg-background w-full"> 28 32 <Stack.Screen 29 33 options={{ 30 - title: 'Home', 31 - headerBackButtonDisplayMode: 'minimal', 34 + title: "Home", 35 + headerBackButtonDisplayMode: "minimal", 32 36 headerShown: false, 33 37 }} 34 38 />
+24 -12
apps/amethyst/components/ui/textarea.tsx
··· 1 - import * as React from 'react'; 2 - import { TextInput, type TextInputProps } from 'react-native'; 3 - import { cn } from '~/lib/utils'; 1 + import * as React from "react"; 2 + import { TextInput, type TextInputProps } from "react-native"; 3 + import { cn } from "~/lib/utils"; 4 4 5 - const Textarea = React.forwardRef<React.ElementRef<typeof TextInput>, TextInputProps>( 6 - ({ className, multiline = true, numberOfLines = 4, placeholderClassName, ...props }, ref) => { 5 + const Textarea = React.forwardRef< 6 + React.ElementRef<typeof TextInput>, 7 + TextInputProps 8 + >( 9 + ( 10 + { 11 + className, 12 + multiline = true, 13 + numberOfLines = 4, 14 + placeholderClassName, 15 + ...props 16 + }, 17 + ref, 18 + ) => { 7 19 return ( 8 20 <TextInput 9 21 ref={ref} 10 22 className={cn( 11 - 'web:flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-base lg:text-sm native:text-lg native:leading-[1.25] text-foreground web:ring-offset-background placeholder:text-muted-foreground web:focus-visible:outline-none web:focus-visible:ring-2 web:focus-visible:ring-ring web:focus-visible:ring-offset-2', 12 - props.editable === false && 'opacity-50 web:cursor-not-allowed', 13 - className 23 + "font-sans web:flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-base lg:text-sm native:text-lg native:leading-[1.25] text-foreground web:ring-offset-background placeholder:text-muted-foreground web:focus-visible:outline-none web:focus-visible:ring-2 web:focus-visible:ring-ring web:focus-visible:ring-offset-2", 24 + props.editable === false && "opacity-50 web:cursor-not-allowed", 25 + className, 14 26 )} 15 - placeholderClassName={cn('text-muted-foreground', placeholderClassName)} 27 + placeholderClassName={cn("text-muted-foreground", placeholderClassName)} 16 28 multiline={multiline} 17 29 numberOfLines={numberOfLines} 18 - textAlignVertical='top' 30 + textAlignVertical="top" 19 31 {...props} 20 32 /> 21 33 ); 22 - } 34 + }, 23 35 ); 24 36 25 - Textarea.displayName = 'Textarea'; 37 + Textarea.displayName = "Textarea"; 26 38 27 39 export { Textarea };
+9 -12
apps/aqua/src/did/web.ts
··· 2 2 3 3 /// Responds with a did:web with a TealFmAppview service at the given domain. 4 4 export function createDidWeb(domain: string, pubKey: string) { 5 + console.log("Requested did web"); 5 6 return { 6 - '@context': [ 7 - 'https://www.w3.org/ns/did/v1', 8 - 'https://w3id.org/security/multikey/v1', 9 - 'https://w3id.org/security/suites/secp256k1-2019/v1', 10 - ], 11 - id: 'did:web:' + domain, 7 + "@context": ["https://www.w3.org/ns/did/v1"], 8 + id: "did:web:" + domain, 12 9 verificationMethod: [ 13 10 { 14 - id: 'did:web:' + domain + '#atproto', 15 - type: 'Multikey', 16 - controller: 'did:web:' + domain, 11 + id: "did:web:" + domain + "#atproto", 12 + type: "Multikey", 13 + controller: "did:web:" + domain, 17 14 publicKeyMultibase: pubKey, 18 15 }, 19 16 ], 20 17 service: [ 21 18 { 22 - id: '#teal_fm_appview', 23 - type: 'TealFmAppView', 24 - serviceEndpoint: 'https://' + domain, 19 + id: "#teal_fm_appview", 20 + type: "TealFmAppView", 21 + serviceEndpoint: "https://" + domain, 25 22 }, 26 23 ], 27 24 };
+13 -10
apps/aqua/src/xrpc/route.ts
··· 1 - import { EnvWithCtx } from '@/ctx'; 2 - import { Hono } from 'hono'; 3 - import getPlay from './feed/getPlay'; 4 - import getActorFeed from './feed/getActorFeed'; 5 - import getProfile from './actor/getProfile'; 6 - import searchActors from './actor/searchActors'; 1 + import { EnvWithCtx } from "@/ctx"; 2 + import { Hono } from "hono"; 3 + import { logger } from "hono/logger"; 4 + import getPlay from "./feed/getPlay"; 5 + import getActorFeed from "./feed/getActorFeed"; 6 + import getProfile from "./actor/getProfile"; 7 + import searchActors from "./actor/searchActors"; 7 8 8 9 // mount this on /xrpc 9 10 const app = new Hono<EnvWithCtx>(); 10 11 11 - app.get('fm.teal.alpha.feed.getPlay', async (c) => c.json(await getPlay(c))); 12 - app.get('fm.teal.alpha.feed.getActorFeed', async (c) => 12 + app.use(logger()); 13 + 14 + app.get("fm.teal.alpha.feed.getPlay", async (c) => c.json(await getPlay(c))); 15 + app.get("fm.teal.alpha.feed.getActorFeed", async (c) => 13 16 c.json(await getActorFeed(c)), 14 17 ); 15 18 16 - app.get('fm.teal.alpha.actor.getProfile', async (c) => 19 + app.get("fm.teal.alpha.actor.getProfile", async (c) => 17 20 c.json(await getProfile(c)), 18 21 ); 19 22 20 - app.get('fm.teal.alpha.actor.searchActors', async (c) => 23 + app.get("fm.teal.alpha.actor.searchActors", async (c) => 21 24 c.json(await searchActors(c)), 22 25 ); 23 26