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