Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/alex_frontend/core/config/apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const appsData: App[] = [
{ name: 'Permasearch', description: 'Explore', path: '/app/permasearch', logo: '/logos/Permasearch.svg' },
{ name: 'Emporium', description: 'Trade', path: '/app/emporium', logo: '/logos/Emporium.svg' },
{ name: 'Pinax', description: 'Upload', path: '/app/pinax', logo: '/logos/Pinax.svg', comingSoon: false },
{ name: 'Sonora', description: 'Audio', path: '/app/sonora', logo: `https://picsum.photos/seed/${Date.now()}/300/300`, comingSoon: true },
{ name: 'Sonora', description: 'Audio', path: '/app/sonora', logo: '/logos/Sonora.svg' },
{ name: 'Syllogos', description: 'Aggregate', path: '/app/syllogos', logo: '/logos/Syllogos.svg', comingSoon: true },
{ name: 'Bibliotheca', description: 'Library', path: '/app/bibliotheca', comingSoon: true },
{ name: 'Dialectica', description: 'Debate', path: '/app/dialectica', comingSoon: true },
Expand Down
31 changes: 21 additions & 10 deletions src/alex_frontend/core/features/sonora/components/AudioCard.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import React, { useRef, useState, useEffect } from "react";
import { FileAudio, HardDrive, Calendar, Play, Pause, Loader2, DollarSign, User } from "lucide-react";
import { Link } from "@tanstack/react-router";
import { useAppDispatch } from "@/store/hooks/useAppDispatch";
import { useAppSelector } from "@/store/hooks/useAppSelector";
import { setSelected, clearSelected } from "../sonoraSlice";
import { Audio } from "../types";
import { shortenPrincipal } from "@/features/perpetua";

const ownerLinks = [
{ to: "/app/sonora/studio/$principal", label: "listings" },
{ to: "/app/sonora/archive/$principal", label: "archive" },
] as const;

interface AudioCardProps {
item: Audio;
Expand Down Expand Up @@ -158,15 +165,6 @@ export const AudioCard: React.FC<AudioCardProps> = ({ item, actions, price, owne
return type || 'No Type';
};

const formatOwner = (ownerPrincipal: string) => {
if (!ownerPrincipal) return 'Unknown';
// Show first 6 and last 4 characters with ellipsis
if (ownerPrincipal.length > 12) {
return `${ownerPrincipal.slice(0, 6)}...${ownerPrincipal.slice(-4)}`;
}
return ownerPrincipal;
};

return (
<div
onClick={handleCardClick}
Expand Down Expand Up @@ -259,7 +257,20 @@ export const AudioCard: React.FC<AudioCardProps> = ({ item, actions, price, owne
{owner && (
<div className="flex items-center gap-1">
<User size={12} />
<span className="font-medium text-foreground">{formatOwner(owner)}</span>
<span className="font-medium text-foreground">{shortenPrincipal(owner)}</span>
{ownerLinks.map((link) => (
<React.Fragment key={link.to}>
<span className="text-muted-foreground/60">·</span>
<Link
to={link.to}
params={{ principal: owner }}
onClick={(e) => e.stopPropagation()}
className="text-primary hover:underline"
>
{link.label}
</Link>
</React.Fragment>
))}
</div>
)}
{isSelected && duration > 0 && (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect } from "react";
import { useCallback } from "react";
import { useAppSelector } from "@/store/hooks/useAppSelector";
import { useAppDispatch } from "@/store/hooks/useAppDispatch";
import { fetchMarketAudioNFTs } from "../marketSlice";
Expand All @@ -9,14 +9,14 @@ export const useMarketAudioNFTs = () => {
const user = useAppSelector((state) => state.auth.user);
const { audios, loading, loadingMore, error, pagination } = useAppSelector((state) => state.sonora.market);

const refreshMarketAudioNFTs = (page = 1, pageSize = 8, appendMode = false) => {
dispatch(fetchMarketAudioNFTs({
page,
pageSize,
appendMode,
currentUserPrincipal: user?.principal
const refreshMarketAudioNFTs = useCallback((page = 1, pageSize = 8, appendMode = false) => {
dispatch(fetchMarketAudioNFTs({
page,
pageSize,
appendMode,
currentUserPrincipal: user?.principal
}));
};
}, [dispatch, user?.principal]);

return {
audios,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { useParams } from "@tanstack/react-router";
import { useAppSelector } from "@/store/hooks/useAppSelector";

export const useSonoraProfileTarget = () => {
const { user } = useAppSelector((state) => state.auth);
const { principal } = useParams({ strict: false });
const targetPrincipal = principal ?? user?.principal;
const isOwner = !principal || principal === user?.principal;
return { targetPrincipal, isOwner };
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect } from "react";
import { useCallback } from "react";
import { useAppSelector } from "@/store/hooks/useAppSelector";
import { useAppDispatch } from "@/store/hooks/useAppDispatch";
import { fetchStudioAudioNFTs } from "../studioSlice";
Expand All @@ -8,9 +8,9 @@ export const useStudioAudioNFTs = () => {
const dispatch = useAppDispatch();
const { audios, loading, loadingMore, error, pagination } = useAppSelector((state) => state.sonora.studio);

const refreshStudioAudioNFTs = (userPrincipal: string, page = 1, pageSize = 8, appendMode = false) => {
const refreshStudioAudioNFTs = useCallback((userPrincipal: string, page = 1, pageSize = 8, appendMode = false) => {
dispatch(fetchStudioAudioNFTs({ userPrincipal, page, pageSize, appendMode }));
};
}, [dispatch]);

return {
audios,
Expand Down
48 changes: 48 additions & 0 deletions src/alex_frontend/core/layouts/SonoraLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from "react";
import { Link, Outlet } from "@tanstack/react-router";

const tabs = [
{ to: "/app/sonora", label: "Browse", exact: true },
{ to: "/app/sonora/record", label: "Record" },
{ to: "/app/sonora/archive", label: "Archive" },
{ to: "/app/sonora/studio", label: "Studio" },
{ to: "/app/sonora/market", label: "Market" },
] as const;

const tabClass = "px-6 py-2 rounded-bordertb font-medium text-tabsheading transition-colors duration-200";
const activeProps = { className: "bg-primary text-primary-foreground shadow-md" };
const inactiveProps = { className: "bg-transparent text-muted-foreground hover:bg-muted hover:text-primary" };

function SonoraLayout() {
return (
<>
<div className="bg-background p-4 flex flex-col gap-8">
<div className="flex flex-col">
<h1 className="text-xxltabsheading font-syne font-bold text-center text-primary">Sonora</h1>
<h3 className="text-smtabsheading text-center text-muted-foreground font-roboto-condensed">Discover, Record, and Trade Audio NFTs</h3>
</div>
<div className="bg-card rounded-bordertb shadow">
<nav className="flex justify-center gap-2 p-2" aria-label="Tabs">
{tabs.map((tab) => (
<Link
key={tab.to}
to={tab.to}
className={tabClass}
activeProps={activeProps}
inactiveProps={inactiveProps}
{...("exact" in tab && tab.exact ? { activeOptions: { exact: true } } : {})}
>
{tab.label}
</Link>
))}
</nav>
</div>
</div>
<main className="flex-1 p-6">
<Outlet />
</main>
</>
);
}

export default SonoraLayout;
37 changes: 37 additions & 0 deletions src/alex_frontend/lbry/public/logos/Sonora.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading