diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 59906136c..6a4a72234 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -119,7 +119,23 @@ export default async function RootLayout({ ? `${navFontFamily}, var(--font-sans, Inter, system-ui, -apple-system, sans-serif)` : "var(--font-sans, Inter, system-ui, -apple-system, sans-serif)"; const navFontColor = systemConfig.ui?.fontColor || "#0f172a"; - const castButtonFontColor = systemConfig.ui?.castButtonFontColor || "#ffffff"; + const castButtonFontColor = + systemConfig.ui?.castButton?.fontColor || + systemConfig.ui?.castButtonFontColor || + "#ffffff"; + const navBackgroundColor = systemConfig.ui?.backgroundColor || "#ffffff"; + const castButtonBackgroundColor = + systemConfig.ui?.castButton?.backgroundColor || + systemConfig.ui?.primaryColor || + "rgb(37, 99, 235)"; + const castButtonHoverColor = + systemConfig.ui?.castButton?.hoverColor || + systemConfig.ui?.primaryHoverColor || + "rgb(29, 78, 216)"; + const castButtonActiveColor = + systemConfig.ui?.castButton?.activeColor || + systemConfig.ui?.primaryActiveColor || + "rgb(30, 64, 175)"; return ( @@ -134,6 +150,10 @@ export default async function RootLayout({ ["--ns-nav-font" as string]: navFontStack, ["--ns-nav-font-color" as string]: navFontColor, ["--ns-cast-button-font-color" as string]: castButtonFontColor, + ["--ns-background-color" as string]: navBackgroundColor, + ["--ns-cast-button-background-color" as string]: castButtonBackgroundColor, + ["--ns-cast-button-hover-color" as string]: castButtonHoverColor, + ["--ns-cast-button-active-color" as string]: castButtonActiveColor, } as React.CSSProperties } > diff --git a/src/app/notifications/page.tsx b/src/app/notifications/page.tsx index 0cdd861fa..abbff9dbf 100644 --- a/src/app/notifications/page.tsx +++ b/src/app/notifications/page.tsx @@ -5,7 +5,7 @@ import { useNotifications } from "@/common/lib/hooks/useNotifications"; import { useCurrentFid } from "@/common/lib/hooks/useCurrentFid"; import { FaCircleExclamation } from "react-icons/fa6"; import { FaReply } from "react-icons/fa6"; -import { +import { HeartIcon, UserPlusIcon, ArrowPathRoundedSquareIcon, @@ -25,6 +25,7 @@ import { import moment from "moment"; import useDelayedValueChange from "@/common/lib/hooks/useDelayedValueChange"; import { useRouter } from "next/navigation"; +import { useUIColors } from "@/common/lib/hooks/useUIColors"; const TAB_OPTIONS = { ALL: "all", @@ -39,6 +40,11 @@ export type NotificationRowProps = React.FC<{ notification: Notification; onSelect: (castHash: string, username: string) => void; isUnseen?: boolean; + fontColor: string; + secondaryTextColor: string; + borderColor: string; + fontFamily: string; + castButtonBackgroundColor: string; }>; // Type guard to safely extract error message @@ -64,13 +70,17 @@ const ErrorPanel = ({ message }: { message: string }) => { ); }; -const FormattedUsersText = ({ users }: { users: User[] }) => { +const FormattedUsersText = ({ users, fontColor }: { users: User[]; fontColor: string }) => { if (users.length === 0) { return "Nobody"; } const firstUserLink = ( - + {users[0].display_name} ); @@ -125,7 +135,16 @@ const getNotificationIcon = (type: NotificationTypeEnum): React.ReactNode => { } }; -const NotificationRow: NotificationRowProps = ({ notification, onSelect, isUnseen = false }) => { +const NotificationRow: NotificationRowProps = ({ + notification, + onSelect, + isUnseen = false, + fontColor, + secondaryTextColor, + borderColor, + fontFamily, + castButtonBackgroundColor, +}) => { const handleClick = useCallback(() => { if (notification.cast?.hash && notification.cast?.author?.username) { onSelect(notification.cast.hash, notification.cast.author.username); @@ -160,11 +179,19 @@ const NotificationRow: NotificationRowProps = ({ notification, onSelect, isUnsee return (
{/* Avatars row (for all notification types) */} {showAvatars && relatedUsers.length > 0 && ( @@ -213,7 +240,10 @@ const NotificationRow: NotificationRowProps = ({ notification, onSelect, isUnsee ))} {/* Show overflow indicator */} {relatedUsers.length > maxAvatarsToShow && ( -
+
+{relatedUsers.length - maxAvatarsToShow}
)} @@ -228,25 +258,30 @@ const NotificationRow: NotificationRowProps = ({ notification, onSelect, isUnsee {/* Header with users and action */}
- +
- {getNotificationActionText(notification.type)} - - + + {getNotificationActionText(notification.type)} + + + • + + {moment(notification.most_recent_timestamp).fromNow()}
{/* Cast content if present */} {notification.cast && ( -
+
@@ -294,6 +329,17 @@ function NotificationsPageContent() { const { mutate: updateLastSeenCursor } = useMutateNotificationsLastSeenCursor(fid, identityPublicKey); const router = useRouter(); + const uiColors = useUIColors(); + const castButtonColors = useMemo( + () => ({ + backgroundColor: uiColors.castButton.backgroundColor, + fontColor: uiColors.castButtonFontColor, + }), + [uiColors.castButton.backgroundColor, uiColors.castButtonFontColor] + ); + const tabBarBackground = "rgba(128, 128, 128, 0.2)"; + const borderColor = "rgba(128, 128, 128, 0.2)"; + const secondaryTextColor = uiColors.fontColor; const onTabChange = useCallback((value: string) => { setTab(value); @@ -385,47 +431,105 @@ function NotificationsPageContent() { ); return ( -
+
{/* Header */} -
-

Notifications

+
+

+ Notifications +

- + All Mentions Follows Recasts Replies Likes @@ -436,7 +540,7 @@ function NotificationsPageContent() { {/* Notifications List */}
- Loading...
}> + Loading...
}> {data?.pages?.map((page, pageIndex) => ( {filterByType(page?.notifications ?? []).map((notification, pageItemIndex) => { @@ -451,6 +555,11 @@ function NotificationsPageContent() { notification={notification} onSelect={onSelectNotification} isUnseen={isUnseen} + fontColor={uiColors.fontColor} + secondaryTextColor={secondaryTextColor} + borderColor={borderColor} + fontFamily={uiColors.fontFamily} + castButtonBackgroundColor={castButtonColors.backgroundColor} key={notificationKey} /> ); @@ -473,7 +582,11 @@ function NotificationsPageContent() {
)} - {!isFetching && !hasNextPage &&

No more notifications

} + {!isFetching && !hasNextPage && ( +

+ No more notifications +

+ )}
)} diff --git a/src/common/components/atoms/icons/ExploreIcon.tsx b/src/common/components/atoms/icons/ExploreIcon.tsx index ee1171185..2e36d86a5 100644 --- a/src/common/components/atoms/icons/ExploreIcon.tsx +++ b/src/common/components/atoms/icons/ExploreIcon.tsx @@ -2,7 +2,7 @@ import React from "react"; const ExploreIcon = () => (
diff --git a/src/common/components/molecules/CastModalHelpers.tsx b/src/common/components/molecules/CastModalHelpers.tsx index c3e0b87f4..4f20da743 100644 --- a/src/common/components/molecules/CastModalHelpers.tsx +++ b/src/common/components/molecules/CastModalHelpers.tsx @@ -5,6 +5,8 @@ import { CastModalPortalProvider, CAST_MODAL_INTERACTIVE_ATTR, } from "@/common/lib/utils/castModalInteractivity"; +import { useUIColors } from "@/common/lib/hooks/useUIColors"; +import { SystemConfig } from "@/config"; export { CAST_MODAL_INTERACTIVE_ATTR }; @@ -33,8 +35,28 @@ export const CastDiscardPrompt: React.FC<{ open: boolean; onClose: () => void; onDiscard: () => void; -}> = ({ open, onClose, onDiscard }) => { + systemConfig?: SystemConfig; +}> = ({ open, onClose, onDiscard, systemConfig }) => { const cancelRef = React.useRef(null); + const uiColors = useUIColors({ systemConfig }); + + const dialogStyles: React.CSSProperties = { + backgroundColor: uiColors.backgroundColor, + color: uiColors.fontColor, + fontFamily: uiColors.fontFamily, + }; + + const cancelButtonStyles: React.CSSProperties = { + backgroundColor: "rgba(128, 128, 128, 0.2)", + borderColor: "rgba(128, 128, 128, 0.2)", + color: uiColors.fontColor, + fontFamily: uiColors.fontFamily, + }; + + const discardButtonStyles: React.CSSProperties = { + color: "#ffffff", + fontFamily: uiColors.fontFamily, + }; React.useEffect(() => { if (open) { @@ -53,13 +75,14 @@ export const CastDiscardPrompt: React.FC<{ -

+

Cancel Cast

-

+

Are you sure you want to discard this draft?

@@ -68,6 +91,7 @@ export const CastDiscardPrompt: React.FC<{ ref={cancelRef} type="button" className="inline-flex items-center justify-center rounded-md border px-3 py-1.5 text-sm" + style={cancelButtonStyles} onClick={onClose} > Cancel @@ -75,6 +99,7 @@ export const CastDiscardPrompt: React.FC<{
-
+
{navigation?.showMusicPlayer !== false && (
)} diff --git a/src/common/components/organisms/Player.tsx b/src/common/components/organisms/Player.tsx index 0b1878d90..36b476abf 100644 --- a/src/common/components/organisms/Player.tsx +++ b/src/common/components/organisms/Player.tsx @@ -36,8 +36,16 @@ type ContentMetadata = { export type PlayerProps = { url: string | string[]; shrunk?: boolean; + fontColor?: string; + fontFamily?: string; + borderColor?: string; }; +const DEFAULT_UI_FONT_COLOR = "var(--ns-nav-font-color, #0f172a)"; +const DEFAULT_UI_FONT_FAMILY = + "var(--ns-nav-font, var(--font-sans, Inter, system-ui, -apple-system, sans-serif))"; +const DEFAULT_BORDER_COLOR = "rgba(128, 128, 128, 0.5)"; + const getToggleIcon = ({ playing, started, ready }): [IconType, string] => { if ((playing && !started) || !ready) { return [LiaCircleNotchSolid, "animate-spin"]; @@ -48,7 +56,13 @@ const getToggleIcon = ({ playing, started, ready }): [IconType, string] => { } }; -export const Player: React.FC = ({ url, shrunk = false }) => { +export const Player: React.FC = ({ + url, + shrunk = false, + fontColor = DEFAULT_UI_FONT_COLOR, + fontFamily = DEFAULT_UI_FONT_FAMILY, + borderColor = DEFAULT_BORDER_COLOR, +}) => { const hasWindow = useHasWindow(); const playerRef = useRef(null); const [muted, setMuted] = useState(true); @@ -148,6 +162,11 @@ export const Player: React.FC = ({ url, shrunk = false }) => { onUnstarted: onUnstarted, }; + const baseTextStyles: React.CSSProperties = { + color: fontColor, + fontFamily, + }; + if (shrunk) { return ( <> @@ -183,9 +202,17 @@ export const Player: React.FC = ({ url, shrunk = false }) => { )} > {playing ? ( - + ) : ( - + )}
)} @@ -215,7 +242,10 @@ export const Player: React.FC = ({ url, shrunk = false }) => { return ( <> -
+
{metadata?.thumbnail && ( poster @@ -223,19 +253,30 @@ export const Player: React.FC = ({ url, shrunk = false }) => {
-

+

{metadata?.title || ""}

-

+

{metadata?.channel || ""}

diff --git a/src/common/components/organisms/TabBar.tsx b/src/common/components/organisms/TabBar.tsx index cde161f65..a81cb81e4 100644 --- a/src/common/components/organisms/TabBar.tsx +++ b/src/common/components/organisms/TabBar.tsx @@ -14,6 +14,7 @@ import ClaimButtonWithModal from "../molecules/ClaimButtonWithModal"; import { useSidebarContext } from "./Sidebar"; import TokenDataHeader from "./TokenDataHeader"; import { validateTabName } from "@/common/utils/tabUtils"; +import { useUIColors } from "@/common/lib/hooks/useUIColors"; interface TabBarProps { inHomebase: boolean; @@ -33,7 +34,7 @@ interface TabBarProps { isEditable?: boolean; } -const isEditableTab = (tabName: string, defaultTab: string) => +const isEditableTab = (tabName: string, defaultTab: string) => tabName !== defaultTab; function TabBar({ @@ -55,6 +56,39 @@ function TabBar({ }: TabBarProps) { const isMobile = useIsMobile(); const { setEditMode } = useSidebarContext(); + const uiColors = useUIColors(); + const castButtonColors = React.useMemo( + () => ({ + backgroundColor: uiColors.castButton.backgroundColor, + hoverColor: uiColors.castButton.hoverColor, + activeColor: uiColors.castButton.activeColor, + fontColor: uiColors.castButtonFontColor, + }), + [ + uiColors.castButton.backgroundColor, + uiColors.castButton.hoverColor, + uiColors.castButton.activeColor, + uiColors.castButtonFontColor, + ], + ); + const customizeButtonStyle = React.useMemo( + () => ({ + "--cast-bg-color": castButtonColors.backgroundColor, + "--cast-hover-color": castButtonColors.hoverColor, + "--cast-active-color": castButtonColors.activeColor, + backgroundColor: "var(--cast-bg-color)", + color: castButtonColors.fontColor, + fontFamily: uiColors.fontFamily, + boxShadow: "none", + }), + [ + castButtonColors.activeColor, + castButtonColors.backgroundColor, + castButtonColors.fontColor, + castButtonColors.hoverColor, + uiColors.fontFamily, + ], + ); const { getIsLoggedIn, getIsInitializing, homebaseLoadTab, setCurrentTabName } = useAppStore((state) => ({ setModalOpen: state.setup.setModalOpen, @@ -243,13 +277,22 @@ function TabBar({ return ( -
+
{isTokenPage && contractAddress && ( -
+
)} -
+
{/* Tabs Section - grows until it hits buttons */}
@@ -289,10 +332,15 @@ function TabBar({ {!inEditMode && ( )} {(inEditMode) && ( @@ -315,4 +363,4 @@ function TabBar({ ); } -export default TabBar; \ No newline at end of file +export default TabBar; diff --git a/src/common/lib/hooks/useUIColors.ts b/src/common/lib/hooks/useUIColors.ts index b786eee4e..1b8f3fb15 100644 --- a/src/common/lib/hooks/useUIColors.ts +++ b/src/common/lib/hooks/useUIColors.ts @@ -18,6 +18,10 @@ export const useUIColors = ({ systemConfig }: UseUIColorsProps = {}) => { let cssFontColor: string | undefined; let cssFontFamily: string | undefined; let cssCastButtonFontColor: string | undefined; + let cssCastButtonBackgroundColor: string | undefined; + let cssCastButtonHoverColor: string | undefined; + let cssCastButtonActiveColor: string | undefined; + let cssBackgroundColor: string | undefined; if (typeof document !== "undefined") { const styles = getComputedStyle(document.body); @@ -25,25 +29,52 @@ export const useUIColors = ({ systemConfig }: UseUIColorsProps = {}) => { cssFontFamily = styles.getPropertyValue("--ns-nav-font")?.trim() || undefined; cssCastButtonFontColor = styles.getPropertyValue("--ns-cast-button-font-color")?.trim() || undefined; + cssCastButtonBackgroundColor = + styles.getPropertyValue("--ns-cast-button-background-color")?.trim() || undefined; + cssCastButtonHoverColor = + styles.getPropertyValue("--ns-cast-button-hover-color")?.trim() || undefined; + cssCastButtonActiveColor = + styles.getPropertyValue("--ns-cast-button-active-color")?.trim() || undefined; + cssBackgroundColor = + styles.getPropertyValue("--ns-background-color")?.trim() || undefined; } const parsedFontFamily = extractFontFamilyFromUrl(ui?.url); + const castButton = { + ...(ui?.castButton || {}), + backgroundColor: + ui?.castButton?.backgroundColor ?? + cssCastButtonBackgroundColor ?? + ui?.primaryColor ?? + "rgb(37, 99, 235)", + hoverColor: + ui?.castButton?.hoverColor ?? + cssCastButtonHoverColor ?? + ui?.primaryHoverColor ?? + "rgb(29, 78, 216)", + activeColor: + ui?.castButton?.activeColor ?? + cssCastButtonActiveColor ?? + ui?.primaryActiveColor ?? + "rgb(30, 64, 175)", + }; return { fontColor: ui?.fontColor || cssFontColor || "#0f172a", - castButtonFontColor: ui?.castButtonFontColor || cssCastButtonFontColor || "#ffffff", + castButtonFontColor: + ui?.castButton?.fontColor || + ui?.castButtonFontColor || + cssCastButtonFontColor || + "#ffffff", fontFamily: parsedFontFamily ? `${parsedFontFamily}, var(--font-sans, sans-serif)` : cssFontFamily || "var(--font-sans, Inter, system-ui, -apple-system, sans-serif)", fontUrl: ui?.url, + backgroundColor: ui?.backgroundColor || cssBackgroundColor || "#ffffff", primaryColor: ui?.primaryColor || "rgb(37, 99, 235)", primaryHoverColor: ui?.primaryHoverColor || "rgb(29, 78, 216)", primaryActiveColor: ui?.primaryActiveColor || "rgb(30, 64, 175)", - castButton: ui?.castButton || { - backgroundColor: ui?.primaryColor || "rgb(37, 99, 235)", - hoverColor: ui?.primaryHoverColor || "rgb(29, 78, 216)", - activeColor: ui?.primaryActiveColor || "rgb(30, 64, 175)", - }, + castButton, }; }, [systemConfig]); }; diff --git a/src/config/systemConfig.ts b/src/config/systemConfig.ts index bf754498b..a267b4444 100644 --- a/src/config/systemConfig.ts +++ b/src/config/systemConfig.ts @@ -44,6 +44,7 @@ export interface UIConfig { primaryHoverColor: string; primaryActiveColor: string; castButton: { + fontColor?: string; backgroundColor: string; hoverColor: string; activeColor: string; diff --git a/src/fidgets/farcaster/components/CastRow.tsx b/src/fidgets/farcaster/components/CastRow.tsx index 289e4f95f..e1d8dfc35 100644 --- a/src/fidgets/farcaster/components/CastRow.tsx +++ b/src/fidgets/farcaster/components/CastRow.tsx @@ -32,6 +32,7 @@ import CreateCast, { DraftType } from "./CreateCast"; import { renderEmbedForUrl, type CastEmbed } from "./Embeds"; import { AnalyticsEvent } from "@/common/constants/analyticsEvents"; import { useToastStore } from "@/common/data/stores/toastStore"; +import { isImageUrl } from "@/common/lib/utils/urls"; function isEmbedUrl(maybe: unknown): maybe is EmbedUrl { return isObject(maybe) && typeof maybe["url"] === "string"; @@ -198,6 +199,15 @@ const CastEmbedsComponent = ({ cast, onSelectCast }: CastEmbedsProps) => { }; const isTwitterEmbed = isTwitterUrl(isEmbedUrl(embed) ? embed.url : embedData.url); + const isCastEmbed = Boolean(embedData.castId); + const isImageEmbed = !!(embedData.url && isImageUrl(embedData.url)); + const embedContainerStyle: React.CSSProperties | undefined = + isCastEmbed || isImageEmbed + ? { + backgroundColor: isCastEmbed ? "rgba(128, 128, 128, 0.5)" : undefined, + borderColor: "rgba(128, 128, 128, 0.2)", + } + : undefined; return (
{ !isTwitterEmbed ? "overflow-hidden max-h-[500px]" : "", embedData.castId ? "max-w-[100%]" : "max-w-max" )} + style={embedContainerStyle} onClick={(event) => { event.stopPropagation(); if (embedData?.castId?.hash) { @@ -233,6 +244,10 @@ const CastEmbedsComponent = ({ cast, onSelectCast }: CastEmbedsProps) => { }; const isTwitterTextUrl = isTwitterUrl(url); + const isImageEmbed = isImageUrl(url); + const embedContainerStyle: React.CSSProperties | undefined = isImageEmbed + ? { borderColor: "rgba(128, 128, 128, 0.2)" } + : undefined; return (
{ !isTwitterTextUrl ? "overflow-hidden max-h-[500px]" : "", "max-w-max" )} + style={embedContainerStyle} > {renderEmbedForUrl(embedData, false)}
diff --git a/src/fidgets/farcaster/components/CreateCast.tsx b/src/fidgets/farcaster/components/CreateCast.tsx index b578ed32a..83b5cba1e 100644 --- a/src/fidgets/farcaster/components/CreateCast.tsx +++ b/src/fidgets/farcaster/components/CreateCast.tsx @@ -60,6 +60,7 @@ import { renderEmbedForUrl } from "./Embeds"; import { useTokenGate } from "@/common/lib/hooks/useTokenGate"; import { type SystemConfig } from "@/config"; +import { useUIColors } from "@/common/lib/hooks/useUIColors"; // SPACE_CONTRACT_ADDR will be loaded when needed (async) // For now, we'll use it in a way that handles the Promise @@ -150,6 +151,23 @@ const CreateCast: React.FC = ({ onShouldConfirmCloseChange, systemConfig, }) => { + const uiColors = useUIColors({ systemConfig }); + const castButtonColors = useMemo( + () => ({ + backgroundColor: uiColors.castButton.backgroundColor, + hoverColor: uiColors.castButton.hoverColor, + activeColor: uiColors.castButton.activeColor, + fontColor: uiColors.castButtonFontColor, + fontFamily: uiColors.fontFamily, + }), + [ + uiColors.castButton.backgroundColor, + uiColors.castButton.hoverColor, + uiColors.castButton.activeColor, + uiColors.castButtonFontColor, + uiColors.fontFamily, + ], + ); const castModalPortalContainer = useCastModalPortalContainer(); const isMobile = useIsMobile(); const [currentMod, setCurrentMod] = useState(null); @@ -773,7 +791,7 @@ const CreateCast: React.FC = ({
{draft.text}
) : (
= ({ {/* Add media button moved to left side on mobile */} {isMobile && (