feat(app): add community links and sidebar home button
This commit is contained in:
@@ -0,0 +1,66 @@
|
|||||||
|
import { useCallback } from "react";
|
||||||
|
import { View } from "react-native";
|
||||||
|
import { StyleSheet } from "react-native-unistyles";
|
||||||
|
import { Heart } from "lucide-react-native";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { GitHubIcon } from "@/components/icons/github-icon";
|
||||||
|
import { DiscordIcon } from "@/components/icons/discord-icon";
|
||||||
|
import { openExternalUrl } from "@/utils/open-external-url";
|
||||||
|
|
||||||
|
const renderGitHubIcon = (color: string) => <GitHubIcon color={color} size={14} />;
|
||||||
|
const renderDiscordIcon = (color: string) => <DiscordIcon color={color} size={14} />;
|
||||||
|
|
||||||
|
export function CommunityLinks() {
|
||||||
|
const handleOpenGitHub = useCallback(() => {
|
||||||
|
void openExternalUrl("https://github.com/getpaseo/paseo");
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleOpenSponsor = useCallback(() => {
|
||||||
|
void openExternalUrl("https://github.com/sponsors/boudra");
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleOpenDiscord = useCallback(() => {
|
||||||
|
void openExternalUrl("https://discord.gg/jz8T2uahpH");
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.row}>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
leftIcon={renderGitHubIcon}
|
||||||
|
onPress={handleOpenGitHub}
|
||||||
|
testID="community-links-github-star"
|
||||||
|
>
|
||||||
|
Star
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
leftIcon={Heart}
|
||||||
|
onPress={handleOpenSponsor}
|
||||||
|
testID="community-links-sponsor"
|
||||||
|
>
|
||||||
|
Sponsor
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
leftIcon={renderDiscordIcon}
|
||||||
|
onPress={handleOpenDiscord}
|
||||||
|
testID="community-links-discord"
|
||||||
|
>
|
||||||
|
Community
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create(() => ({
|
||||||
|
row: {
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 0,
|
||||||
|
},
|
||||||
|
}));
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import Svg, { Path } from "react-native-svg";
|
||||||
|
|
||||||
|
interface DiscordIconProps {
|
||||||
|
size?: number;
|
||||||
|
color?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DiscordIcon({ size = 16, color = "currentColor" }: DiscordIconProps) {
|
||||||
|
return (
|
||||||
|
<Svg width={size} height={size} viewBox="0 0 24 24" fill={color}>
|
||||||
|
<Path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z" />
|
||||||
|
</Svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { router, usePathname } from "expo-router";
|
import { router, usePathname } from "expo-router";
|
||||||
import { FolderPlus, MessagesSquare, Settings, X } from "lucide-react-native";
|
import { FolderPlus, Home, MessagesSquare, Settings, X } from "lucide-react-native";
|
||||||
import {
|
import {
|
||||||
type Dispatch,
|
type Dispatch,
|
||||||
memo,
|
memo,
|
||||||
@@ -58,6 +58,7 @@ import { resolveActiveHost } from "@/utils/active-host";
|
|||||||
import { formatConnectionStatus } from "@/utils/daemons";
|
import { formatConnectionStatus } from "@/utils/daemons";
|
||||||
import { useWindowControlsPadding } from "@/utils/desktop-window";
|
import { useWindowControlsPadding } from "@/utils/desktop-window";
|
||||||
import {
|
import {
|
||||||
|
buildHostOpenProjectRoute,
|
||||||
buildHostSessionsRoute,
|
buildHostSessionsRoute,
|
||||||
buildSettingsRoute,
|
buildSettingsRoute,
|
||||||
mapPathnameToServer,
|
mapPathnameToServer,
|
||||||
@@ -94,6 +95,7 @@ interface SidebarSharedProps {
|
|||||||
handleRefresh: () => void;
|
handleRefresh: () => void;
|
||||||
handleHostSelect: (nextServerId: string) => void;
|
handleHostSelect: (nextServerId: string) => void;
|
||||||
handleOpenProject: () => void;
|
handleOpenProject: () => void;
|
||||||
|
handleHome: () => void;
|
||||||
handleSettings: () => void;
|
handleSettings: () => void;
|
||||||
renderHostOption: (input: {
|
renderHostOption: (input: {
|
||||||
option: ComboboxOption;
|
option: ComboboxOption;
|
||||||
@@ -223,6 +225,17 @@ export const LeftSidebar = memo(function LeftSidebar({
|
|||||||
router.push(buildSettingsRoute());
|
router.push(buildSettingsRoute());
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleHomeMobile = useCallback(() => {
|
||||||
|
if (!activeServerId) return;
|
||||||
|
showMobileAgent();
|
||||||
|
router.push(buildHostOpenProjectRoute(activeServerId));
|
||||||
|
}, [activeServerId, showMobileAgent]);
|
||||||
|
|
||||||
|
const handleHomeDesktop = useCallback(() => {
|
||||||
|
if (!activeServerId) return;
|
||||||
|
router.push(buildHostOpenProjectRoute(activeServerId));
|
||||||
|
}, [activeServerId]);
|
||||||
|
|
||||||
const handleViewMoreNavigate = useCallback(() => {
|
const handleViewMoreNavigate = useCallback(() => {
|
||||||
if (!activeServerId) {
|
if (!activeServerId) {
|
||||||
return;
|
return;
|
||||||
@@ -272,6 +285,7 @@ export const LeftSidebar = memo(function LeftSidebar({
|
|||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
closeToAgent={showMobileAgent}
|
closeToAgent={showMobileAgent}
|
||||||
handleOpenProject={handleOpenProjectMobile}
|
handleOpenProject={handleOpenProjectMobile}
|
||||||
|
handleHome={handleHomeMobile}
|
||||||
handleSettings={handleSettingsMobile}
|
handleSettings={handleSettingsMobile}
|
||||||
handleViewMoreNavigate={handleViewMoreNavigate}
|
handleViewMoreNavigate={handleViewMoreNavigate}
|
||||||
/>
|
/>
|
||||||
@@ -284,6 +298,7 @@ export const LeftSidebar = memo(function LeftSidebar({
|
|||||||
insetsTop={insets.top}
|
insetsTop={insets.top}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
handleOpenProject={handleOpenProjectDesktop}
|
handleOpenProject={handleOpenProjectDesktop}
|
||||||
|
handleHome={handleHomeDesktop}
|
||||||
handleSettings={handleSettingsDesktop}
|
handleSettings={handleSettingsDesktop}
|
||||||
handleViewMore={handleViewMoreNavigate}
|
handleViewMore={handleViewMoreNavigate}
|
||||||
/>
|
/>
|
||||||
@@ -414,6 +429,7 @@ function SidebarFooter({
|
|||||||
handleHostSelect,
|
handleHostSelect,
|
||||||
renderHostOption,
|
renderHostOption,
|
||||||
handleOpenProject,
|
handleOpenProject,
|
||||||
|
handleHome,
|
||||||
handleSettings,
|
handleSettings,
|
||||||
}: {
|
}: {
|
||||||
theme: SidebarTheme;
|
theme: SidebarTheme;
|
||||||
@@ -427,6 +443,7 @@ function SidebarFooter({
|
|||||||
handleHostSelect: (nextServerId: string) => void;
|
handleHostSelect: (nextServerId: string) => void;
|
||||||
renderHostOption: SidebarSharedProps["renderHostOption"];
|
renderHostOption: SidebarSharedProps["renderHostOption"];
|
||||||
handleOpenProject: () => void;
|
handleOpenProject: () => void;
|
||||||
|
handleHome: () => void;
|
||||||
handleSettings: () => void;
|
handleSettings: () => void;
|
||||||
}) {
|
}) {
|
||||||
const newAgentKeys = useShortcutKeys("new-agent");
|
const newAgentKeys = useShortcutKeys("new-agent");
|
||||||
@@ -456,6 +473,13 @@ function SidebarFooter({
|
|||||||
<AddProjectTooltipContent newAgentKeys={newAgentKeys} />
|
<AddProjectTooltipContent newAgentKeys={newAgentKeys} />
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
<FooterIconButton
|
||||||
|
onPress={handleHome}
|
||||||
|
testID="sidebar-home"
|
||||||
|
accessibilityLabel="Home"
|
||||||
|
icon={Home}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
<FooterIconButton
|
<FooterIconButton
|
||||||
onPress={handleSettings}
|
onPress={handleSettings}
|
||||||
testID="sidebar-settings"
|
testID="sidebar-settings"
|
||||||
@@ -501,6 +525,7 @@ function MobileSidebar({
|
|||||||
handleHostSelect,
|
handleHostSelect,
|
||||||
renderHostOption,
|
renderHostOption,
|
||||||
handleOpenProject,
|
handleOpenProject,
|
||||||
|
handleHome,
|
||||||
handleSettings,
|
handleSettings,
|
||||||
insetsTop,
|
insetsTop,
|
||||||
insetsBottom,
|
insetsBottom,
|
||||||
@@ -729,6 +754,7 @@ function MobileSidebar({
|
|||||||
handleHostSelect={handleHostSelect}
|
handleHostSelect={handleHostSelect}
|
||||||
renderHostOption={renderHostOption}
|
renderHostOption={renderHostOption}
|
||||||
handleOpenProject={handleOpenProject}
|
handleOpenProject={handleOpenProject}
|
||||||
|
handleHome={handleHome}
|
||||||
handleSettings={handleSettings}
|
handleSettings={handleSettings}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
@@ -758,6 +784,7 @@ function DesktopSidebar({
|
|||||||
handleHostSelect,
|
handleHostSelect,
|
||||||
renderHostOption,
|
renderHostOption,
|
||||||
handleOpenProject,
|
handleOpenProject,
|
||||||
|
handleHome,
|
||||||
handleSettings,
|
handleSettings,
|
||||||
insetsTop,
|
insetsTop,
|
||||||
isOpen,
|
isOpen,
|
||||||
@@ -871,6 +898,7 @@ function DesktopSidebar({
|
|||||||
handleHostSelect={handleHostSelect}
|
handleHostSelect={handleHostSelect}
|
||||||
renderHostOption={renderHostOption}
|
renderHostOption={renderHostOption}
|
||||||
handleOpenProject={handleOpenProject}
|
handleOpenProject={handleOpenProject}
|
||||||
|
handleHome={handleHome}
|
||||||
handleSettings={handleSettings}
|
handleSettings={handleSettings}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
|||||||
import { useRouter } from "expo-router";
|
import { useRouter } from "expo-router";
|
||||||
import { FolderOpen, Inbox, Plug, Smartphone } from "lucide-react-native";
|
import { FolderOpen, Inbox, Plug, Smartphone } from "lucide-react-native";
|
||||||
import { PaseoLogo } from "@/components/icons/paseo-logo";
|
import { PaseoLogo } from "@/components/icons/paseo-logo";
|
||||||
|
import { CommunityLinks } from "@/components/community-links";
|
||||||
import { MenuHeader } from "@/components/headers/menu-header";
|
import { MenuHeader } from "@/components/headers/menu-header";
|
||||||
import { useOpenProjectPicker } from "@/hooks/use-open-project-picker";
|
import { useOpenProjectPicker } from "@/hooks/use-open-project-picker";
|
||||||
import { usePanelStore } from "@/stores/panel-store";
|
import { usePanelStore } from "@/stores/panel-store";
|
||||||
@@ -106,6 +107,9 @@ export function OpenProjectScreen({ serverId }: { serverId: string }) {
|
|||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
<View style={styles.communityRow}>
|
||||||
|
<CommunityLinks />
|
||||||
|
</View>
|
||||||
<PairDeviceModal
|
<PairDeviceModal
|
||||||
visible={isPairDeviceOpen}
|
visible={isPairDeviceOpen}
|
||||||
onClose={handleClosePairDevice}
|
onClose={handleClosePairDevice}
|
||||||
@@ -227,4 +231,17 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
fontSize: theme.fontSize.sm,
|
fontSize: theme.fontSize.sm,
|
||||||
lineHeight: 18,
|
lineHeight: 18,
|
||||||
},
|
},
|
||||||
|
communityRow: {
|
||||||
|
position: "absolute",
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: {
|
||||||
|
xs: HEADER_INNER_HEIGHT_MOBILE + HEADER_TOP_PADDING_MOBILE + theme.spacing[2],
|
||||||
|
md: HEADER_INNER_HEIGHT + theme.spacing[2],
|
||||||
|
},
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 0,
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ import { AddHostModal } from "@/components/add-host-modal";
|
|||||||
import { PairLinkModal } from "@/components/pair-link-modal";
|
import { PairLinkModal } from "@/components/pair-link-modal";
|
||||||
import { KeyboardShortcutsSection } from "@/screens/settings/keyboard-shortcuts-section";
|
import { KeyboardShortcutsSection } from "@/screens/settings/keyboard-shortcuts-section";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { CommunityLinks } from "@/components/community-links";
|
||||||
import { SegmentedControl } from "@/components/ui/segmented-control";
|
import { SegmentedControl } from "@/components/ui/segmented-control";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@@ -457,6 +458,9 @@ function AboutSection({ appVersionText, isDesktopApp }: AboutSectionProps) {
|
|||||||
</View>
|
</View>
|
||||||
{isDesktopApp ? <DesktopAppUpdateRow /> : null}
|
{isDesktopApp ? <DesktopAppUpdateRow /> : null}
|
||||||
</View>
|
</View>
|
||||||
|
<View style={styles.aboutCommunity}>
|
||||||
|
<CommunityLinks />
|
||||||
|
</View>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1267,6 +1271,9 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
fontSize: theme.fontSize.xs,
|
fontSize: theme.fontSize.xs,
|
||||||
marginTop: theme.spacing[1],
|
marginTop: theme.spacing[1],
|
||||||
},
|
},
|
||||||
|
aboutCommunity: {
|
||||||
|
marginTop: theme.spacing[4],
|
||||||
|
},
|
||||||
aboutUpdateActions: {
|
aboutUpdateActions: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
|
|||||||
Reference in New Issue
Block a user