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
8 changes: 5 additions & 3 deletions app/(app)/[appName]/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { View, Text, ScrollView, Linking } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { useLocalSearchParams, useRouter } from "expo-router";
import { useTranslation } from "react-i18next";
import { Inbox, ChevronRight, AlertCircle } from "lucide-react-native";
import { PressableCard } from "~/components/ui/PressableCard";
import { EmptyState } from "~/components/ui/EmptyState";
Expand All @@ -20,6 +21,7 @@ import { useThemeColors } from "~/lib/theme-colors";
export default function AppHomeScreen() {
const { appName } = useLocalSearchParams<{ appName: string }>();
const router = useRouter();
const { t } = useTranslation();
const { accent } = useThemeColors();
const { app, isLoading, error } = useApp(appName);

Expand Down Expand Up @@ -116,16 +118,16 @@ export default function AppHomeScreen() {
<EmptyState
icon={AlertCircle}
variant="error"
title="Couldn't Load App"
title={t("empty.loadApp")}
description={error.message}
/>
</View>
) : navigation.length === 0 ? (
<View className="pt-20">
<EmptyState
icon={Inbox}
title="No Navigation"
description="This app hasn't published a navigation menu yet."
title={t("empty.appNavTitle")}
description={t("empty.appNavDesc")}
/>
</View>
) : (
Expand Down
6 changes: 3 additions & 3 deletions app/(app)/packages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export default function PackagesScreen() {
<EmptyState
icon={Package}
variant="error"
title="Couldn't Load Packages"
title={t("empty.loadPackages")}
description={error.message}
actionLabel="Retry"
onAction={refetch}
Expand All @@ -78,8 +78,8 @@ export default function PackagesScreen() {
<View className="pt-24">
<EmptyState
icon={Package}
title="No Packages"
description="No packages are installed yet."
title={t("empty.packagesTitle")}
description={t("empty.packagesDesc")}
/>
</View>
) : (
Expand Down
4 changes: 3 additions & 1 deletion app/(app)/page/[id].tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useEffect, useState } from "react";
import { SafeAreaView } from "react-native-safe-area-context";
import { useLocalSearchParams } from "expo-router";
import { useTranslation } from "react-i18next";
import { useClient } from "@objectstack/client-react";
import { AlertCircle } from "lucide-react-native";
import { ScreenHeader } from "~/components/common/ScreenHeader";
Expand All @@ -20,6 +21,7 @@ import {
*/
export default function SDUIPageScreen() {
const { id } = useLocalSearchParams<{ id: string }>();
const { t } = useTranslation();
const client = useClient();
const [schema, setSchema] = useState<PageSchema | null>(null);
const [isLoading, setIsLoading] = useState(true);
Expand Down Expand Up @@ -64,7 +66,7 @@ export default function SDUIPageScreen() {
<EmptyState
icon={AlertCircle}
variant="error"
title="Couldn't Load Page"
title={t("empty.loadPage")}
description={error.message}
/>
) : schema ? (
Expand Down
14 changes: 8 additions & 6 deletions app/flows/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { View, Text, ScrollView } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { useRouter } from "expo-router";
import { useTranslation } from "react-i18next";
import { Workflow } from "lucide-react-native";
import { ScreenHeader } from "~/components/common/ScreenHeader";
import { PressableCard } from "~/components/ui/PressableCard";
Expand Down Expand Up @@ -46,33 +47,34 @@ function FlowCard({ flow, onPress }: { flow: FlowDefinition; onPress: () => void
*/
export default function FlowsScreen() {
const router = useRouter();
const { t } = useTranslation();
const { data: flows, isLoading, error, refetch, isRefetching } = useFlows();

const count = flows?.length ?? 0;

return (
<SafeAreaView className="flex-1 bg-background" edges={["left", "right"]}>
<ScreenHeader
title="Automation Flows"
subtitle={count > 0 ? `${count} flow${count === 1 ? "" : "s"}` : undefined}
title={t("empty.flowsHeader")}
subtitle={count > 0 ? t("empty.flowCount", { count }) : undefined}
/>
{isLoading ? (
<ListSkeleton count={6} />
) : error ? (
<EmptyState
icon={Workflow}
variant="error"
title="Couldn't load flows"
title={t("empty.loadFlows")}
description={error.message}
actionLabel="Retry"
actionLabel={t("common.retry")}
onAction={() => void refetch()}
actionLoading={isRefetching}
/>
) : count === 0 ? (
<EmptyState
icon={Workflow}
title="No flows defined"
description="This server has no automation flows yet."
title={t("empty.flowsTitle")}
description={t("empty.flowsDesc")}
/>
) : (
<ScrollView className="flex-1" contentContainerClassName="px-4 pt-4 pb-8">
Expand Down
4 changes: 3 additions & 1 deletion components/renderers/CalendarViewRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Pressable,
} from "react-native";
import { ChevronLeft, ChevronRight } from "lucide-react-native";
import { useTranslation } from "react-i18next";
import { useThemeColors } from "~/lib/theme-colors";
import { Skeleton } from "~/components/ui/Skeleton";
import { cn } from "~/lib/utils";
Expand Down Expand Up @@ -113,6 +114,7 @@ export function CalendarViewRenderer({
initialYear,
initialMonth,
}: CalendarViewRendererProps) {
const { t } = useTranslation();
const { accent } = useThemeColors();
const now = new Date();
const [year, setYear] = useState(initialYear ?? now.getFullYear());
Expand Down Expand Up @@ -257,7 +259,7 @@ export function CalendarViewRenderer({
Events for {selectedDate}
</Text>
{selectedEvents.length === 0 ? (
<Text className="text-sm text-muted-foreground">No events</Text>
<Text className="text-sm text-muted-foreground">{t("empty.calendarEvents")}</Text>
) : (
selectedEvents.map((ev) => (
<Pressable
Expand Down
15 changes: 11 additions & 4 deletions components/renderers/ChartViewRenderer.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useMemo } from "react";
import { View, Text, ScrollView } from "react-native";
import { BarChart3, TrendingUp, PieChart, Activity, AlertCircle } from "lucide-react-native";
import { useTranslation } from "react-i18next";
import { useThemeColors } from "~/lib/theme-colors";
import { Card, CardHeader, CardTitle, CardContent } from "~/components/ui/Card";
import { Skeleton } from "~/components/ui/Skeleton";
Expand Down Expand Up @@ -202,12 +203,17 @@ function FunnelChartView({ data, height }: { data: AnalyticsDataPoint[]; height:
/* Chart dispatcher */
/* ------------------------------------------------------------------ */

function renderChart(type: ChartType, data: AnalyticsDataPoint[], height: number) {
function renderChart(
type: ChartType,
data: AnalyticsDataPoint[],
height: number,
emptyLabel: string,
) {
if (data.length === 0) {
return (
<View className="items-center justify-center py-12">
<BarChart3 size={40} color="#94a3b8" />
<Text className="mt-3 text-sm text-muted-foreground">No data available</Text>
<Text className="mt-3 text-sm text-muted-foreground">{emptyLabel}</Text>
</View>
);
}
Expand Down Expand Up @@ -244,6 +250,7 @@ export function ChartViewRenderer({
error,
chartHeight = 220,
}: ChartViewRendererProps) {
const { t } = useTranslation();
if (isLoading) {
return (
<ScrollView className="flex-1" contentContainerStyle={{ padding: 16 }}>
Expand Down Expand Up @@ -277,7 +284,7 @@ export function ChartViewRenderer({
<EmptyState
icon={AlertCircle}
variant="error"
title="Couldn't Load Chart"
title={t("empty.loadChart")}
description={error.message}
/>
);
Expand All @@ -295,7 +302,7 @@ export function ChartViewRenderer({
</View>
</CardHeader>
<CardContent>
{renderChart(chartType, data, chartHeight)}
{renderChart(chartType, data, chartHeight, t("empty.noDataAvailable"))}
</CardContent>
</Card>
</ScrollView>
Expand Down
12 changes: 8 additions & 4 deletions components/renderers/DashboardViewRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { Card, CardHeader, CardTitle, CardContent } from "~/components/ui/Card";
import { Skeleton } from "~/components/ui/Skeleton";
import { WidgetChart } from "./charts/WidgetChart";
import { useTranslation } from "react-i18next";
import { formatByPattern, formatCurrency, formatNumber } from "~/lib/formatting";
import { useThemeColors } from "~/lib/theme-colors";
import type { DashboardMeta, DashboardWidgetMeta } from "./types";
Expand Down Expand Up @@ -185,6 +186,7 @@ function ListWidget({
widget: DashboardWidgetMeta;
data?: WidgetDataPayload;
}) {
const { t } = useTranslation();
const { accent } = useThemeColors();
const records = data?.records ?? [];

Expand All @@ -202,7 +204,7 @@ function ListWidget({
{data?.isLoading ? (
<ActivityIndicator size="small" />
) : records.length === 0 ? (
<Text className="text-sm text-muted-foreground">No data</Text>
<Text className="text-sm text-muted-foreground">{t("empty.noData")}</Text>
) : (
<View className="gap-2">
{records.slice(0, 5).map((rec, idx) => {
Expand Down Expand Up @@ -243,6 +245,7 @@ function ChartWidget({
widget: DashboardWidgetMeta;
data?: WidgetDataPayload;
}) {
const { t } = useTranslation();
const { accent } = useThemeColors();
const chartType = String(widget.chartConfig?.type ?? widget.type ?? "bar");
const colors = Array.isArray(widget.chartConfig?.colors)
Expand Down Expand Up @@ -275,7 +278,7 @@ function ChartWidget({
<View className="items-center justify-center py-8">
<BarChart3 size={48} color="#94a3b8" />
<Text className="mt-3 text-sm text-muted-foreground">
No data to chart
{t("empty.noDataToChart")}
</Text>
</View>
)}
Expand Down Expand Up @@ -344,6 +347,7 @@ export function DashboardViewRenderer({
isLoading = false,
onWidgetPress: _onWidgetPress,
}: DashboardViewRendererProps) {
const { t } = useTranslation();
const { width: screenWidth } = useWindowDimensions();
const numColumns = screenWidth > SINGLE_COLUMN_MAX ? 2 : 1;

Expand All @@ -357,9 +361,9 @@ export function DashboardViewRenderer({
<View className="h-20 w-20 items-center justify-center rounded-2xl bg-muted">
<Activity size={40} color="#94a3b8" />
</View>
<Text className="mt-5 text-lg font-semibold text-foreground">No Dashboard</Text>
<Text className="mt-5 text-lg font-semibold text-foreground">{t("empty.dashboardTitle")}</Text>
<Text className="mt-2 text-center text-sm text-muted-foreground">
No dashboard widgets have been configured.
{t("empty.dashboardDesc")}
</Text>
</View>
);
Expand Down
8 changes: 5 additions & 3 deletions components/renderers/GalleryViewRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { cn } from "~/lib/utils";
import { EmptyState } from "~/components/common/EmptyState";
import { Skeleton } from "~/components/ui/Skeleton";
import { Image as ImageIcon } from "lucide-react-native";
import { useTranslation } from "react-i18next";

/* ------------------------------------------------------------------ */
/* Props */
Expand Down Expand Up @@ -92,8 +93,9 @@ export function GalleryViewRenderer({
aspectRatio = 1,
isLoading,
onCardPress,
emptyMessage = "No items to display",
emptyMessage,
}: GalleryViewRendererProps) {
const { t } = useTranslation();
const renderCard = useCallback(
({ item }: { item: Record<string, unknown> }) => {
const uri = resolveImageUri(item, imageField);
Expand Down Expand Up @@ -160,8 +162,8 @@ export function GalleryViewRenderer({
return (
<EmptyState
icon={<ImageIcon size={40} color="#94a3b8" />}
title="No Items"
description={emptyMessage}
title={t("empty.galleryTitle")}
description={emptyMessage ?? t("empty.noData")}
/>
);
}
Expand Down
8 changes: 5 additions & 3 deletions components/renderers/GanttViewRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useMemo, useCallback } from "react";
import { View, Text, Pressable, ScrollView } from "react-native";
import { EmptyState } from "~/components/common/EmptyState";
import { GanttChartSquare } from "lucide-react-native";
import { useTranslation } from "react-i18next";
import { Skeleton } from "~/components/ui/Skeleton";

/* ------------------------------------------------------------------ */
Expand Down Expand Up @@ -153,8 +154,9 @@ export function GanttViewRenderer({
colorField,
isLoading,
onTaskPress,
emptyMessage = "No scheduled items",
emptyMessage,
}: GanttViewRendererProps) {
const { t } = useTranslation();
const tasks = useMemo(
() => buildGanttTasks(records, { labelField, startField, endField, colorField }),
[records, labelField, startField, endField, colorField],
Expand Down Expand Up @@ -202,8 +204,8 @@ export function GanttViewRenderer({
return (
<EmptyState
icon={<GanttChartSquare size={40} color="#94a3b8" />}
title="Nothing Scheduled"
description={emptyMessage}
title={t("empty.ganttTitle")}
description={emptyMessage ?? t("empty.noData")}
/>
);
}
Expand Down
6 changes: 4 additions & 2 deletions components/renderers/ImageGallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
useWindowDimensions,
} from "react-native";
import { X, Download, Share2, Image as ImageIcon } from "lucide-react-native";
import { useTranslation } from "react-i18next";
import { Skeleton } from "~/components/ui/Skeleton";
import { EmptyState } from "~/components/ui/EmptyState";

Expand Down Expand Up @@ -52,6 +53,7 @@ export function ImageGallery({
onDownload,
onShare,
}: ImageGalleryProps) {
const { t } = useTranslation();
const { width: screenWidth } = useWindowDimensions();
const [selectedIndex, setSelectedIndex] = useState<number | null>(null);

Expand Down Expand Up @@ -94,8 +96,8 @@ export function ImageGallery({
return (
<EmptyState
icon={ImageIcon}
title="No Images"
description="There are no images to display yet."
title={t("empty.imagesTitle")}
description={t("empty.imagesDesc")}
/>
);
}
Expand Down
4 changes: 3 additions & 1 deletion components/renderers/KanbanViewRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
Pressable,
} from "react-native";
import { GripVertical, Plus } from "lucide-react-native";
import { useTranslation } from "react-i18next";
import { Skeleton } from "~/components/ui/Skeleton";
import type { FieldDefinition } from "./types";

Expand Down Expand Up @@ -139,6 +140,7 @@ function KanbanColumnView({
onCardPress?: (record: Record<string, unknown>) => void;
onAddCard?: () => void;
}) {
const { t } = useTranslation();
return (
<View className="mr-3 w-64 rounded-xl bg-muted/30 p-2">
{/* Column header */}
Expand Down Expand Up @@ -176,7 +178,7 @@ function KanbanColumnView({
)}
showsVerticalScrollIndicator={false}
ListEmptyComponent={
<Text className="py-4 text-center text-xs text-muted-foreground">No items</Text>
<Text className="py-4 text-center text-xs text-muted-foreground">{t("empty.kanbanItems")}</Text>
}
/>
</View>
Expand Down
Loading
Loading