diff --git a/apps/app/tailwind.config.ts b/apps/app/tailwind.config.ts
index aa06cb5d86..d2162302c2 100644
--- a/apps/app/tailwind.config.ts
+++ b/apps/app/tailwind.config.ts
@@ -1,19 +1,15 @@
import Tiptap from "@hypr/tiptap/editor/tailwind.config";
-import UI from "@hypr/ui/tailwind.config";
import typography from "@tailwindcss/typography";
import type { Config } from "tailwindcss";
const config = {
- ...UI,
content: [
...Tiptap.content,
- ...UI.content,
"src/**/*.{js,ts,jsx,tsx}",
"index.html",
],
theme: {
extend: {
- ...UI.theme?.extend,
fontFamily: {
"racing-sans": ["Racing Sans One", "cursive"],
},
diff --git a/apps/desktop/src/components/settings-panel/sidebar/extensions-view.tsx b/apps/desktop/src/components/settings-panel/sidebar/extensions-view.tsx
index a571105e2d..d2e1922e97 100644
--- a/apps/desktop/src/components/settings-panel/sidebar/extensions-view.tsx
+++ b/apps/desktop/src/components/settings-panel/sidebar/extensions-view.tsx
@@ -29,7 +29,6 @@ export function ExtensionsView({
const selectedExtension: Extension | null = null;
const handleExtensionSelect = (extension: Extension) => {
// TODO: Implement extension selection
- console.log("Selected extension:", extension);
};
return (
diff --git a/apps/desktop/src/components/settings-panel/views/team.tsx b/apps/desktop/src/components/settings-panel/views/team.tsx
index 6bac7b847b..eb2ecb43d5 100644
--- a/apps/desktop/src/components/settings-panel/views/team.tsx
+++ b/apps/desktop/src/components/settings-panel/views/team.tsx
@@ -57,7 +57,6 @@ export default function TeamComponent() {
const handleDelete = (member: Member) => {
// TODO: Implement delete functionality
- console.log("Delete member:", member);
};
return (
diff --git a/apps/desktop/src/lib/date.ts b/apps/desktop/src/lib/date.ts
index 22c912a23c..d98960f31e 100644
--- a/apps/desktop/src/lib/date.ts
+++ b/apps/desktop/src/lib/date.ts
@@ -27,8 +27,6 @@ export function formatDateHeader(date: Date): string {
const todayStart = startOfToday();
const daysDiff = differenceInCalendarDays(todayStart, date, tzOptions);
- console.log(todayStart, date, daysDiff);
-
if (daysDiff > 1 && daysDiff <= 7) {
if (isThisWeek(date, tzOptions)) {
return format(date, "EEEE", tzOptions);
diff --git a/apps/mobile/package.json b/apps/mobile/package.json
index feaa19de54..471b7653eb 100644
--- a/apps/mobile/package.json
+++ b/apps/mobile/package.json
@@ -13,12 +13,12 @@
"dependencies": {
"@date-fns/tz": "^1.2.0",
"@hookform/resolvers": "^3.10.0",
- "@huggingface/languages": "^1.0.0",
"@hypr/plugin-auth": "workspace:^",
"@hypr/plugin-db": "workspace:^",
"@hypr/plugin-listener": "workspace:^",
"@hypr/plugin-misc": "workspace:^",
"@hypr/plugin-template": "workspace:^",
+ "@hypr/tiptap": "workspace:^",
"@hypr/ui": "workspace:^",
"@stackflow/config": "^1.2.1",
"@stackflow/core": "^1.2.0",
@@ -39,6 +39,7 @@
"zustand": "^5.0.3"
},
"devDependencies": {
+ "@tailwindcss/typography": "^0.5.16",
"@tauri-apps/cli": "^2.3.1",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
diff --git a/apps/mobile/src/components/home/event-item.tsx b/apps/mobile/src/components/home/event-item.tsx
new file mode 100644
index 0000000000..1b48ecbfa5
--- /dev/null
+++ b/apps/mobile/src/components/home/event-item.tsx
@@ -0,0 +1,31 @@
+import { useQuery } from "@tanstack/react-query";
+import { formatRemainingTime } from "../../utils/date";
+
+import { commands as dbCommands, type Event } from "@hypr/plugin-db";
+
+export function EventItem({ event, onSelect }: { event: Event; onSelect: (sessionId: string) => void }) {
+ const session = useQuery({
+ queryKey: ["event-session", event.id],
+ queryFn: async () => dbCommands.getSession({ calendarEventId: event.id }),
+ });
+
+ const handleClick = () => {
+ if (session.data) {
+ onSelect(session.data.id);
+ }
+ };
+
+ return (
+
+ );
+}
diff --git a/apps/mobile/src/components/home/index.ts b/apps/mobile/src/components/home/index.ts
new file mode 100644
index 0000000000..8f1b0e4246
--- /dev/null
+++ b/apps/mobile/src/components/home/index.ts
@@ -0,0 +1,2 @@
+export * from "./event-item";
+export * from "./note-item";
diff --git a/apps/mobile/src/components/home/note-item.tsx b/apps/mobile/src/components/home/note-item.tsx
new file mode 100644
index 0000000000..805d04ab6e
--- /dev/null
+++ b/apps/mobile/src/components/home/note-item.tsx
@@ -0,0 +1,28 @@
+import { type Session } from "@hypr/plugin-db";
+import { format } from "date-fns";
+
+export function NoteItem({
+ session,
+ onSelect,
+}: {
+ session: Session;
+ onSelect: () => void;
+}) {
+ const sessionDate = new Date(session.created_at);
+
+ return (
+
+ );
+}
diff --git a/apps/mobile/src/components/note/index.ts b/apps/mobile/src/components/note/index.ts
new file mode 100644
index 0000000000..e67b4690b5
--- /dev/null
+++ b/apps/mobile/src/components/note/index.ts
@@ -0,0 +1,2 @@
+export * from "./note-content";
+export * from "./note-info";
diff --git a/apps/mobile/src/components/note/note-content.tsx b/apps/mobile/src/components/note/note-content.tsx
new file mode 100644
index 0000000000..52669243e5
--- /dev/null
+++ b/apps/mobile/src/components/note/note-content.tsx
@@ -0,0 +1,27 @@
+import { useRef } from "react";
+
+import { type Session } from "@hypr/plugin-db";
+import Editor, { TiptapEditor } from "@hypr/tiptap/editor";
+
+interface ContentProps {
+ session: Session;
+}
+
+export function NoteContent({ session }: ContentProps) {
+ const editorRef = useRef<{ editor: TiptapEditor }>(null);
+
+ return (
+
+
+ {
+ // TODO: implement
+ }}
+ initialContent={session.enhanced_memo_html || session.raw_memo_html}
+ autoFocus={false}
+ />
+
+
+ );
+}
diff --git a/apps/mobile/src/components/note/note-info.tsx b/apps/mobile/src/components/note/note-info.tsx
new file mode 100644
index 0000000000..f15803ccc8
--- /dev/null
+++ b/apps/mobile/src/components/note/note-info.tsx
@@ -0,0 +1,94 @@
+import { Users2Icon } from "lucide-react";
+
+import type { Session } from "@hypr/plugin-db";
+import { Popover, PopoverContent, PopoverTrigger } from "@hypr/ui/components/ui/popover";
+
+interface SessionInfoProps {
+ session: Session;
+}
+
+export function NoteInfo({ session }: SessionInfoProps) {
+ const hasParticipants = session.conversations.length > 0
+ && session.conversations.some((conv) => conv.diarizations.length > 0);
+
+ const participantsCount = hasParticipants
+ ? session.conversations.flatMap((conv) => conv.diarizations).length
+ : 0;
+
+ const currentDate = new Date().toLocaleDateString("en-US", {
+ month: "short",
+ day: "numeric",
+ year: "numeric",
+ });
+
+ const uniqueParticipants = hasParticipants
+ ? Array.from(
+ new Set(
+ session.conversations.flatMap((conv) => conv.diarizations.map((d) => d.speaker)),
+ ),
+ )
+ : [];
+
+ return (
+
+
+ {session.title || "Untitled"}
+
+
+
+
+ {currentDate}
+
+
+ {hasParticipants && (
+
+
+
+
+
+
+ {participantsCount} Participant
+ {participantsCount !== 1 ? "s" : ""}
+
+
+
+
+
+
+ {uniqueParticipants.map((participant, index) => (
+
+
+
+
+
+ {participant.substring(0, 2).toUpperCase()}
+
+
+
+ {participant}
+
+
+
+
+ ))}
+
+
+
+
+
+ )}
+
+
+ );
+}
diff --git a/apps/mobile/src/components/recordings/index.ts b/apps/mobile/src/components/recordings/index.ts
new file mode 100644
index 0000000000..42789a1e8e
--- /dev/null
+++ b/apps/mobile/src/components/recordings/index.ts
@@ -0,0 +1 @@
+export * from "./recording-item";
diff --git a/apps/mobile/src/components/recordings/recording-item.tsx b/apps/mobile/src/components/recordings/recording-item.tsx
new file mode 100644
index 0000000000..46411d8f16
--- /dev/null
+++ b/apps/mobile/src/components/recordings/recording-item.tsx
@@ -0,0 +1,44 @@
+import { format } from "date-fns";
+import { CheckIcon } from "lucide-react";
+import { type LocalRecording } from "../../mock/recordings";
+import { formatFileSize, formatRecordingDuration } from "../../utils";
+
+export const RecordingItem = ({
+ recording,
+ onSelect,
+ isSelected = false,
+}: {
+ recording: LocalRecording;
+ onSelect: () => void;
+ isSelected?: boolean;
+}) => {
+ const recordingDate = new Date(recording.created_at);
+
+ return (
+
+
+
+ );
+};
diff --git a/apps/mobile/src/main.tsx b/apps/mobile/src/main.tsx
index c390231917..53fda1ab46 100644
--- a/apps/mobile/src/main.tsx
+++ b/apps/mobile/src/main.tsx
@@ -5,7 +5,6 @@ import "./styles/globals.css";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { StrictMode, Suspense } from "react";
import ReactDOM from "react-dom/client";
-import { HyprProvider } from "./contexts/hypr";
import { Stack } from "./stackflow";
const queryClient = new QueryClient();
@@ -17,9 +16,7 @@ if (!rootElement.innerHTML) {
-
-
-
+
,
diff --git a/apps/mobile/src/mock/home.ts b/apps/mobile/src/mock/home.ts
new file mode 100644
index 0000000000..7acb052a82
--- /dev/null
+++ b/apps/mobile/src/mock/home.ts
@@ -0,0 +1,146 @@
+import type { Event, Session } from "@hypr/plugin-db";
+
+export const mockSessions: Session[] = [
+ {
+ id: "session-1",
+ created_at: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(),
+ visited_at: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000 + 75 * 60 * 1000).toISOString(),
+ user_id: "user-123",
+ calendar_event_id: "event-456",
+ title: "Weekly Team Standup",
+ audio_local_path: "/recordings/session-1.mp3",
+ audio_remote_path: "https://storage.hyprnote.com/user-123/recordings/session-1.mp3",
+ raw_memo_html: "Discussed project timeline updates and resource allocation.
",
+ enhanced_memo_html:
+ "Weekly Team Standup
Discussed project timeline updates and resource allocation.
- Frontend milestones delayed by 2 days
- Backend on track
- Need to onboard new designer by next week
",
+ conversations: [],
+ },
+ {
+ id: "session-2",
+ created_at: new Date(Date.now() - 21 * 24 * 60 * 60 * 1000).toISOString(),
+ visited_at: new Date(Date.now() - 21 * 24 * 60 * 60 * 1000 + 90 * 60 * 1000).toISOString(),
+ user_id: "user-123",
+ calendar_event_id: null,
+ title: "Product Strategy Brainstorm",
+ audio_local_path: "/recordings/session-2.mp3",
+ audio_remote_path: null,
+ raw_memo_html: "Brainstormed new feature ideas for Q3.
",
+ enhanced_memo_html:
+ "Product Strategy Brainstorm
Brainstormed new feature ideas for Q3.
- Voice memo transcription
- Calendar integration
- AI summary generation
",
+ conversations: [],
+ },
+ {
+ id: "session-3",
+ created_at: new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString(),
+ visited_at: new Date(Date.now() - 14 * 24 * 60 * 60 * 1000 + 45 * 60 * 1000).toISOString(),
+ user_id: "user-123",
+ calendar_event_id: "event-789",
+ title: "1:1 with Manager",
+ audio_local_path: "/recordings/session-3.mp3",
+ audio_remote_path: "https://storage.hyprnote.com/user-123/recordings/session-3.mp3",
+ raw_memo_html: "Quarterly review prep and career development discussion.
",
+ enhanced_memo_html:
+ "1:1 with Manager
Quarterly review prep and career development discussion.
Action Items:
- Update portfolio with recent projects
- Review Q2 OKRs
- Schedule training for new tech stack
",
+ conversations: [],
+ },
+ {
+ id: "session-4",
+ created_at: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(),
+ visited_at: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000 + 75 * 60 * 1000).toISOString(),
+ user_id: "user-123",
+ calendar_event_id: null,
+ title: "Design System Planning",
+ audio_local_path: "/recordings/session-4.mp3",
+ audio_remote_path: "https://storage.hyprnote.com/user-123/recordings/session-4.mp3",
+ raw_memo_html: "Initial planning for unified design system.
",
+ enhanced_memo_html:
+ "Design System Planning
Initial planning for unified design system.
- Component inventory needed
- Color palette standardization
- Typography review
",
+ conversations: [],
+ },
+ {
+ id: "session-5",
+ created_at: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(),
+ visited_at: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000 + 75 * 60 * 1000).toISOString(),
+ user_id: "user-123",
+ calendar_event_id: "event-012",
+ title: "Client Presentation Prep",
+ audio_local_path: "/recordings/session-5.mp3",
+ audio_remote_path: null,
+ raw_memo_html: "Outline for upcoming client presentation.
",
+ enhanced_memo_html:
+ "Client Presentation Prep
Outline for upcoming client presentation.
- Project overview
- Timeline updates
- Budget considerations
- Next steps
",
+ conversations: [],
+ },
+ {
+ id: "session-6",
+ created_at: new Date().toISOString(),
+ visited_at: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
+ user_id: "user-123",
+ calendar_event_id: "event-345",
+ title: "Weekly Status Update",
+ audio_local_path: "/recordings/session-6.mp3",
+ audio_remote_path: null,
+ raw_memo_html: "Weekly progress report with team members.
",
+ enhanced_memo_html:
+ "Weekly Status Update
Weekly progress report with team members.
- Completed API integration
- Started user testing
- Identified performance bottlenecks
",
+ conversations: [],
+ },
+];
+
+export const mockEvents: Event[] = [
+ {
+ id: "event-future-1",
+ user_id: "user-123",
+ tracking_id: "track-123",
+ calendar_id: "cal-123",
+ name: "Quarterly Planning Meeting",
+ note: "Prepare Q3 roadmap discussion points",
+ start_date: new Date(Date.now() + 86400000).toISOString(),
+ end_date: new Date(Date.now() + 86400000 + 3600000).toISOString(),
+ google_event_url: "https://calendar.google.com/event?id=abc123",
+ },
+ {
+ id: "event-future-2",
+ user_id: "user-123",
+ tracking_id: "track-456",
+ calendar_id: "cal-123",
+ name: "Product Demo with Sales Team",
+ note: "Show latest feature updates",
+ start_date: new Date(Date.now() + 172800000).toISOString(),
+ end_date: new Date(Date.now() + 172800000 + 5400000).toISOString(),
+ google_event_url: "https://calendar.google.com/event?id=def456",
+ },
+ {
+ id: "event-future-3",
+ user_id: "user-123",
+ tracking_id: "track-789",
+ calendar_id: "cal-123",
+ name: "Weekly Team Standup",
+ note: "Regular team sync",
+ start_date: new Date(Date.now() + 259200000).toISOString(),
+ end_date: new Date(Date.now() + 259200000 + 1800000).toISOString(),
+ google_event_url: "https://calendar.google.com/event?id=ghi789",
+ },
+ {
+ id: "event-future-4",
+ user_id: "user-123",
+ tracking_id: "track-012",
+ calendar_id: "cal-123",
+ name: "1:1 with Manager",
+ note: "Quarterly review prep",
+ start_date: new Date(Date.now() + 345600000).toISOString(),
+ end_date: new Date(Date.now() + 345600000 + 2700000).toISOString(),
+ google_event_url: "https://calendar.google.com/event?id=jkl012",
+ },
+ {
+ id: "event-future-5",
+ user_id: "user-123",
+ tracking_id: "track-345",
+ calendar_id: "cal-123",
+ name: "Client Presentation Prep",
+ note: "Finalize slides",
+ start_date: new Date(Date.now() + 432000000).toISOString(),
+ end_date: new Date(Date.now() + 432000000 + 3600000).toISOString(),
+ google_event_url: null,
+ },
+];
diff --git a/apps/mobile/src/mock/index.ts b/apps/mobile/src/mock/index.ts
new file mode 100644
index 0000000000..cd80bc5e97
--- /dev/null
+++ b/apps/mobile/src/mock/index.ts
@@ -0,0 +1,2 @@
+export * from "./home";
+export * from "./settings";
diff --git a/apps/mobile/src/mock/recordings.ts b/apps/mobile/src/mock/recordings.ts
new file mode 100644
index 0000000000..7b31267a75
--- /dev/null
+++ b/apps/mobile/src/mock/recordings.ts
@@ -0,0 +1,84 @@
+export interface LocalRecording {
+ id: string;
+ filename: string;
+ title: string;
+ duration: number;
+ size: number;
+ created_at: string;
+ path: string;
+}
+
+export const localRecordings: LocalRecording[] = [
+ {
+ id: "rec-1",
+ filename: "recording_20250310_121501.m4a",
+ title: "Team Meeting Notes",
+ duration: 1823,
+ size: 15728640,
+ created_at: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(),
+ path: "/storage/emulated/0/Recordings/recording_20250310_121501.m4a",
+ },
+ {
+ id: "rec-2",
+ filename: "recording_20250309_153022.m4a",
+ title: "Project Brainstorming",
+ duration: 2712,
+ size: 22020096,
+ created_at: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString(),
+ path: "/storage/emulated/0/Recordings/recording_20250309_153022.m4a",
+ },
+ {
+ id: "rec-3",
+ filename: "recording_20250308_091534.m4a",
+ title: "Interview with Client",
+ duration: 3541,
+ size: 29360128,
+ created_at: new Date(Date.now() - 4 * 24 * 60 * 60 * 1000).toISOString(),
+ path: "/storage/emulated/0/Recordings/recording_20250308_091534.m4a",
+ },
+ {
+ id: "rec-4",
+ filename: "recording_20250307_143012.m4a",
+ title: "Personal Notes",
+ duration: 901,
+ size: 8388608,
+ created_at: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString(),
+ path: "/storage/emulated/0/Recordings/recording_20250307_143012.m4a",
+ },
+ {
+ id: "rec-5",
+ filename: "recording_20250312_103045.m4a",
+ title: "Quick Idea",
+ duration: 185,
+ size: 2097152,
+ created_at: new Date(Date.now() - 0.3 * 24 * 60 * 60 * 1000).toISOString(),
+ path: "/storage/emulated/0/Recordings/recording_20250312_103045.m4a",
+ },
+ {
+ id: "rec-6",
+ filename: "recording_20250311_163211.m4a",
+ title: "Research Notes",
+ duration: 1256,
+ size: 10485760,
+ created_at: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(),
+ path: "/storage/emulated/0/Recordings/recording_20250311_163211.m4a",
+ },
+ {
+ id: "rec-7",
+ filename: "recording_20250301_092233.m4a",
+ title: "Monthly Planning",
+ duration: 4532,
+ size: 36700160,
+ created_at: new Date(Date.now() - 11 * 24 * 60 * 60 * 1000).toISOString(),
+ path: "/storage/emulated/0/Recordings/recording_20250301_092233.m4a",
+ },
+ {
+ id: "rec-8",
+ filename: "recording_20250215_143355.m4a",
+ title: "Product Review",
+ duration: 2103,
+ size: 18874368,
+ created_at: new Date(Date.now() - 25 * 24 * 60 * 60 * 1000).toISOString(),
+ path: "/storage/emulated/0/Recordings/recording_20250215_143355.m4a",
+ },
+];
diff --git a/apps/mobile/src/mock/settings.ts b/apps/mobile/src/mock/settings.ts
new file mode 100644
index 0000000000..c9df53178e
--- /dev/null
+++ b/apps/mobile/src/mock/settings.ts
@@ -0,0 +1,4 @@
+export const mockUserSettings = {
+ alertEnhancingDone: true,
+ remindUpcomingEvents: false,
+};
diff --git a/apps/mobile/src/stackflow.config.ts b/apps/mobile/src/stackflow.config.ts
index ddcf03a66f..09f3cf96de 100644
--- a/apps/mobile/src/stackflow.config.ts
+++ b/apps/mobile/src/stackflow.config.ts
@@ -1,31 +1,31 @@
import { defineConfig } from "@stackflow/config";
-import { homeActivityLoader } from "./views/home";
-import { noteActivityLoader } from "./views/note";
-import { profileActivityLoader } from "./views/profile";
-import { settingsActivityLoader } from "./views/settings";
+import { homeLoader } from "./views/home";
+import { noteLoader } from "./views/note";
+import { recordingsLoader } from "./views/recordings";
+import { settingsLoader } from "./views/settings";
export const config = defineConfig({
transitionDuration: 250,
activities: [
{
- name: "HomeActivity",
- loader: homeActivityLoader,
+ name: "HomeView",
+ loader: homeLoader,
},
{
- name: "NoteActivity",
- loader: noteActivityLoader,
+ name: "NoteView",
+ loader: noteLoader,
},
{
- name: "LoginActivity",
+ name: "LoginView",
},
{
- name: "SettingsActivity",
- loader: settingsActivityLoader,
+ name: "SettingsView",
+ loader: settingsLoader,
},
{
- name: "ProfileActivity",
- loader: profileActivityLoader,
+ name: "RecordingsView",
+ loader: recordingsLoader,
},
],
- initialActivity: () => "HomeActivity",
+ initialActivity: () => "HomeView",
});
diff --git a/apps/mobile/src/stackflow.tsx b/apps/mobile/src/stackflow.tsx
index 8073b2e2f2..f8bc5dd3a8 100644
--- a/apps/mobile/src/stackflow.tsx
+++ b/apps/mobile/src/stackflow.tsx
@@ -2,20 +2,20 @@ import { basicUIPlugin } from "@stackflow/plugin-basic-ui";
import { basicRendererPlugin } from "@stackflow/plugin-renderer-basic";
import { stackflow } from "@stackflow/react/future";
import { config } from "./stackflow.config";
-import { HomeActivity } from "./views/home";
-import { LoginActivity } from "./views/login";
-import { NoteActivity } from "./views/note";
-import { ProfileActivity } from "./views/profile";
-import { SettingsActivity } from "./views/settings";
+import { HomeView } from "./views/home";
+import { LoginView } from "./views/login";
+import { NoteView } from "./views/note";
+import { RecordingsView } from "./views/recordings";
+import { SettingsView } from "./views/settings";
export const { Stack } = stackflow({
config,
components: {
- HomeActivity,
- NoteActivity,
- LoginActivity,
- SettingsActivity,
- ProfileActivity,
+ HomeView,
+ NoteView,
+ LoginView,
+ SettingsView,
+ RecordingsView,
},
plugins: [basicRendererPlugin(), basicUIPlugin({ theme: "cupertino" })],
});
diff --git a/apps/mobile/src/utils/date.ts b/apps/mobile/src/utils/date.ts
index 22c912a23c..6735f7fdfa 100644
--- a/apps/mobile/src/utils/date.ts
+++ b/apps/mobile/src/utils/date.ts
@@ -1,6 +1,8 @@
import { tz } from "@date-fns/tz";
import { differenceInCalendarDays, format, isThisWeek, isThisYear, isToday, isYesterday, startOfToday } from "date-fns";
+import { type LocalRecording } from "../mock/recordings";
+
import { type Session } from "@hypr/plugin-db";
export type GroupedSessions = Record<
@@ -27,8 +29,6 @@ export function formatDateHeader(date: Date): string {
const todayStart = startOfToday();
const daysDiff = differenceInCalendarDays(todayStart, date, tzOptions);
- console.log(todayStart, date, daysDiff);
-
if (daysDiff > 1 && daysDiff <= 7) {
if (isThisWeek(date, tzOptions)) {
return format(date, "EEEE", tzOptions);
@@ -63,7 +63,7 @@ export function formatRemainingTime(date: Date): string {
}
}
-export function groupSessionsByDate(sessions: Session[]): GroupedSessions {
+export function groupNotesByDate(sessions: Session[]): GroupedSessions {
return sessions.reduce((groups, session) => {
const date = new Date(session.created_at);
const dateKey = format(date, "yyyy-MM-dd");
@@ -80,6 +80,34 @@ export function groupSessionsByDate(sessions: Session[]): GroupedSessions {
}, {});
}
-export function getSortedDates(groupedSessions: GroupedSessions): string[] {
+export function getSortedDatesForNotes(groupedSessions: GroupedSessions): string[] {
return Object.keys(groupedSessions).sort((a, b) => b.localeCompare(a));
}
+
+export const groupRecordingsByDate = (recordings: LocalRecording[]) => {
+ const groups: Record = {};
+
+ recordings.forEach(recording => {
+ const date = new Date(recording.created_at);
+ const dateKey = date.toISOString().split("T")[0];
+
+ if (!groups[dateKey]) {
+ groups[dateKey] = {
+ date,
+ recordings: [],
+ };
+ }
+
+ groups[dateKey].recordings.push(recording);
+ });
+
+ return groups;
+};
+
+export const getSortedDatesForRecordings = (
+ groupedRecordings: Record,
+) => {
+ return Object.keys(groupedRecordings).sort((a, b) => {
+ return new Date(b).getTime() - new Date(a).getTime();
+ });
+};
diff --git a/apps/mobile/src/utils/file.ts b/apps/mobile/src/utils/file.ts
new file mode 100644
index 0000000000..396aae8da1
--- /dev/null
+++ b/apps/mobile/src/utils/file.ts
@@ -0,0 +1,27 @@
+/**
+ * Format duration in seconds to mm:ss or hh:mm:ss
+ * @param seconds Duration in seconds
+ * @returns Formatted duration string
+ */
+export const formatRecordingDuration = (seconds: number): string => {
+ const hours = Math.floor(seconds / 3600);
+ const minutes = Math.floor((seconds % 3600) / 60);
+ const remainingSeconds = seconds % 60;
+
+ if (hours > 0) {
+ return `${hours}:${minutes.toString().padStart(2, "0")}:${remainingSeconds.toString().padStart(2, "0")}`;
+ }
+
+ return `${minutes}:${remainingSeconds.toString().padStart(2, "0")}`;
+};
+
+/**
+ * Format file size in bytes to human-readable format
+ * @param bytes File size in bytes
+ * @returns Formatted file size string (B, KB, MB)
+ */
+export const formatFileSize = (bytes: number): string => {
+ if (bytes < 1024) return bytes + " B";
+ if (bytes < 1048576) return (bytes / 1024).toFixed(1) + " KB";
+ return (bytes / 1048576).toFixed(1) + " MB";
+};
diff --git a/apps/mobile/src/utils/index.ts b/apps/mobile/src/utils/index.ts
new file mode 100644
index 0000000000..ed596f5d61
--- /dev/null
+++ b/apps/mobile/src/utils/index.ts
@@ -0,0 +1,2 @@
+export * from "./date";
+export * from "./file";
diff --git a/apps/mobile/src/views/home.tsx b/apps/mobile/src/views/home.tsx
index 1445d536b7..3ad5864dfb 100644
--- a/apps/mobile/src/views/home.tsx
+++ b/apps/mobile/src/views/home.tsx
@@ -1,68 +1,61 @@
import type { ActivityLoaderArgs } from "@stackflow/config";
import { AppScreen } from "@stackflow/plugin-basic-ui";
-import { ActivityComponentType, useFlow } from "@stackflow/react/future";
-import { useQuery } from "@tanstack/react-query";
-import { format, isFuture } from "date-fns";
-import { CalendarIcon, Settings } from "lucide-react";
-import { useHypr } from "../contexts/hypr";
-import { formatDateHeader, formatRemainingTime, getSortedDates, groupSessionsByDate } from "../utils/date";
-
-import { commands as dbCommands, type Event, type Session } from "@hypr/plugin-db";
-import { Avatar, AvatarFallback, AvatarImage } from "@hypr/ui/components/ui/avatar";
+import { ActivityComponentType, useFlow, useLoaderData } from "@stackflow/react/future";
+import {
+ AudioLinesIcon,
+ CalendarIcon,
+ ChevronDownIcon,
+ ChevronRightIcon,
+ MicIcon,
+ Settings,
+ SquarePenIcon,
+} from "lucide-react";
+import * as React from "react";
+
+import { BottomSheet, BottomSheetContent } from "@hypr/ui/components/ui/bottom-sheet";
+import { EventItem, NoteItem } from "../components/home";
+import { mockEvents, mockSessions } from "../mock";
+import { formatDateHeader, getSortedDatesForNotes, groupNotesByDate } from "../utils/date";
+
+import { type Session } from "@hypr/plugin-db";
import { Button } from "@hypr/ui/components/ui/button";
-export function homeActivityLoader({}: ActivityLoaderArgs<"HomeActivity">) {
- return {};
+export function homeLoader({}: ActivityLoaderArgs<"HomeView">) {
+ // TODO: For the upcoming events in mobile, let's just fetch < 1 week
+ return {
+ upcomingEvents: mockEvents,
+ notes: mockSessions,
+ };
}
-export const HomeActivity: ActivityComponentType<"HomeActivity"> = () => {
- const { userId } = useHypr();
+export const HomeView: ActivityComponentType<"HomeView"> = () => {
+ const { upcomingEvents, notes } = useLoaderData();
+ const [sheetOpen, setSheetOpen] = React.useState(false);
+ const [upcomingExpanded, setUpcomingExpanded] = React.useState(true);
+
const { push } = useFlow();
- const events = useQuery({
- queryKey: ["events"],
- queryFn: async () => {
- const events = await dbCommands.listEvents(userId);
- const upcomingEvents = events.filter((event) => {
- return isFuture(new Date(event.start_date));
- });
- return upcomingEvents;
- },
- });
-
- const sessions = useQuery({
- queryKey: ["sessions"],
- queryFn: () => dbCommands.listSessions(null),
- });
-
- const groupedSessions = groupSessionsByDate(sessions.data ?? []);
- const sortedDates = getSortedDates(groupedSessions);
+ const groupedSessions = groupNotesByDate(notes ?? []);
+ const sortedDates = getSortedDatesForNotes(groupedSessions);
const handleClickNote = (id: string) => {
- push("NoteActivity", { id });
+ push("NoteView", { id });
};
- const handleClickNew = () => {
- push("NoteActivity", { id: "new" });
+ const handleUploadFile = () => {
+ push("RecordingsView", {});
+ setSheetOpen(false);
};
- const handleClickProfile = () => {
- push("ProfileActivity", {});
+ const handleStartRecord = () => {
+ push("NoteView", { id: "new" });
+ setSheetOpen(false);
};
const handleClickSettings = () => {
- push("SettingsActivity", {});
+ push("SettingsView", {});
};
- const LeftButton = () => (
-
- );
-
const RightButton = () => (