@@ -988,7 +983,7 @@ const DesktopView = ({ isPreview = false }: { isPreview?: boolean }) => {
{/* Content Area */}
{(isExiting || !isLanding) && renderActiveView()}
diff --git a/src/components/views/MobileView.tsx b/src/components/views/MobileView.tsx
index e54972c..6efa4f8 100644
--- a/src/components/views/MobileView.tsx
+++ b/src/components/views/MobileView.tsx
@@ -12,15 +12,12 @@ import {
FileText,
Video,
Settings,
- LogOut,
Bell
} from "lucide-react";
import { MobileLayout } from "@/components/layout/MobileLayout";
import Workspace from "@/components/workspace/Workspace";
-import DashboardView from "./DashboardView";
import TasksView from "./TasksView";
import PeopleView from "./PeopleView";
-import ActivityLogView from "./ActivityLogView";
import CalendarView from "./CalendarView";
import { NotesView } from "@/components/notes/NotesView";
import MeetView from "./MeetView";
@@ -32,15 +29,22 @@ import { WifiOff, RefreshCw } from "lucide-react";
import { useToast } from "@/hooks/use-toast";
import { Button } from "@/components/ui/button";
import { ScrollArea } from "@/components/ui/scroll-area";
-import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
-import { Card, CardContent } from "@/components/ui/card";
-import { Badge } from "@/components/ui/badge";
+import MobileActivityLogView from "@/components/views/mobile/MobileActivityLogView";
+import MobileDashboardView from "@/components/views/mobile/MobileDashboardView";
const MobileView = () => {
const [activeTab, setActiveTab] = useState("Home");
const [currentUser, setCurrentUser] = useState
(null);
const { isError: userMeError, refetch: refetchMe } = useMe();
const [usersList, setUsersList] = useState([]);
+ const [activityLogs, setActivityLogs] = useState([]);
+ const [leaderTasks, setLeaderTasks] = useState([]);
+ const [teamTasks, setTeamTasks] = useState([]);
+ const [teamSessions, setTeamSessions] = useState([]);
+ const [ownedTeams, setOwnedTeams] = useState([]);
+ const [myTeams, setMyTeams] = useState([]);
+ const [elapsedTime, setElapsedTime] = useState("00:00:00");
+ const [sessionStartTime, setSessionStartTime] = useState(null);
const navigate = useNavigate();
const { toast } = useToast();
@@ -84,12 +88,169 @@ const MobileView = () => {
navigate(`/projects/${id}`, { state: { from: '/dashboard/workspace' } });
};
- const handleSignOut = async () => {
+ const buildActivityLogTasks = (projects: any[]) => {
+ return projects.flatMap((project: any) =>
+ (project.steps || []).flatMap((step: any) =>
+ (step.tasks || []).map((task: any) => ({
+ ...task,
+ projectId: project._id || project.id,
+ projectName: project.name,
+ githubRepoName: project.githubRepoName,
+ githubRepoOwner: project.githubRepoOwner,
+ githubRepo: project.githubRepo,
+ repoIds: project.githubRepoIds,
+ projectOwnerId: project.ownerUid || project.ownerId,
+ }))
+ )
+ );
+ };
+
+ const filterCommitCapableTasks = (tasks: any[], userId: string) => {
+ return tasks.filter((task: any) => {
+ const assignedTo = task?.assignedTo;
+ const assignedUserIds = Array.isArray(task?.assignedUserIds) ? task.assignedUserIds : [];
+ const hasRepoLink = Boolean(
+ task?.githubRepoOwner ||
+ task?.githubRepoName ||
+ task?.githubRepo ||
+ (Array.isArray(task?.repoIds) && task.repoIds.length > 0)
+ );
+ const hasCommitCode = Boolean(task?.commitCode);
+
+ return hasRepoLink && hasCommitCode && (assignedTo === userId || assignedUserIds.includes(userId));
+ });
+ };
+
+ useEffect(() => {
+ const storedSession = localStorage.getItem("currentSession");
+ if (!storedSession) { return; }
try {
- await signOutAndClearState(auth);
- navigate("/login");
+ const parsed = JSON.parse(storedSession);
+ if (parsed?.startTime) {
+ setSessionStartTime(new Date(parsed.startTime));
+ }
+ } catch {
+ // Ignore invalid local session payloads.
+ }
+ }, []);
+
+ useEffect(() => {
+ if (!sessionStartTime) { return; }
+ const timer = setInterval(() => {
+ const now = new Date();
+ const diff = Math.max(0, Math.floor((now.getTime() - sessionStartTime.getTime()) / 1000));
+ const hours = Math.floor(diff / 3600).toString().padStart(2, "0");
+ const minutes = Math.floor((diff % 3600) / 60).toString().padStart(2, "0");
+ const seconds = (diff % 60).toString().padStart(2, "0");
+ setElapsedTime(`${hours}:${minutes}:${seconds}`);
+ }, 1000);
+
+ return () => clearInterval(timer);
+ }, [sessionStartTime]);
+
+ useEffect(() => {
+ if (activeTab !== "Activity" || !currentUser) { return; }
+
+ let cancelled = false;
+ const fetchActivityData = async () => {
+ try {
+ const token = await currentUser.getIdToken();
+ const [sessionsRes, projectsRes, ownedTeamsRes, myTeamsRes] = await Promise.all([
+ fetch(`${API_BASE_URL}/api/sessions/${currentUser.uid}`, { headers: { Authorization: `Bearer ${token}` } }),
+ fetch(`${API_BASE_URL}/api/projects`, { headers: { Authorization: `Bearer ${token}` } }),
+ fetch(`${API_BASE_URL}/api/teams/owned`, { headers: { Authorization: `Bearer ${token}` } }),
+ fetch(`${API_BASE_URL}/api/teams/mine`, { headers: { Authorization: `Bearer ${token}` } }),
+ ]);
+
+ if (cancelled) { return; }
+
+ if (sessionsRes.ok) {
+ const logsData = await sessionsRes.json();
+ setActivityLogs(Array.isArray(logsData) ? logsData : []);
+ }
+
+ if (projectsRes.ok) {
+ const projects = await projectsRes.json();
+ if (Array.isArray(projects)) {
+ const allTasks = buildActivityLogTasks(projects);
+ setTeamTasks(allTasks);
+ const myTasks = filterCommitCapableTasks(allTasks, currentUser.uid);
+ const receivedTasks = myTasks.filter(
+ (task: any) => task.assignedBy !== currentUser.uid && task.createdBy !== currentUser.uid
+ );
+ setLeaderTasks(receivedTasks);
+ }
+ }
+
+ if (ownedTeamsRes.ok) {
+ const teams = await ownedTeamsRes.json();
+ setOwnedTeams(Array.isArray(teams) ? teams : []);
+ }
+
+ if (myTeamsRes.ok) {
+ const teams = await myTeamsRes.json();
+ setMyTeams(Array.isArray(teams) ? teams : []);
+ }
+
+ if (usersList.length > 0) {
+ const teamSessionsRes = await fetch(`${API_BASE_URL}/api/sessions/batch`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
+ body: JSON.stringify({ userIds: usersList.map((user) => user.uid) }),
+ });
+ if (!cancelled && teamSessionsRes.ok) {
+ const sessions = await teamSessionsRes.json();
+ setTeamSessions(Array.isArray(sessions) ? sessions : []);
+ }
+ }
+ } catch (error) {
+ if (!cancelled) {
+ console.error("Failed to load mobile activity data:", error);
+ }
+ }
+ };
+
+ fetchActivityData();
+ const intervalId = setInterval(fetchActivityData, 30000);
+ return () => {
+ cancelled = true;
+ clearInterval(intervalId);
+ };
+ }, [activeTab, currentUser, usersList]);
+
+ const handleDeleteLog = async (logId: string) => {
+ if (!currentUser) { return; }
+ try {
+ const token = await currentUser.getIdToken();
+ const response = await fetch(`${API_BASE_URL}/api/sessions/${logId}`, {
+ method: "DELETE",
+ headers: { Authorization: `Bearer ${token}` },
+ });
+ if (response.ok) {
+ setActivityLogs((prev) => prev.filter((log) => log._id !== logId));
+ } else {
+ throw new Error("Failed to delete log");
+ }
+ } catch (error) {
+ toast({ title: "Error", description: "Failed to delete log.", variant: "destructive" });
+ }
+ };
+
+ const handleClearLogs = async () => {
+ if (!currentUser) { return; }
+ try {
+ const token = await currentUser.getIdToken();
+ const response = await fetch(`${API_BASE_URL}/api/sessions/user/${currentUser.uid}`, {
+ method: "DELETE",
+ headers: { Authorization: `Bearer ${token}` },
+ });
+ if (response.ok) {
+ setActivityLogs([]);
+ } else {
+ throw new Error("Failed to clear logs");
+ }
} catch (error) {
- console.error("Error signing out", error);
+ toast({ title: "Error", description: "Failed to clear logs.", variant: "destructive" });
}
};
@@ -109,7 +270,7 @@ const MobileView = () => {
const renderContent = () => {
switch (activeTab) {
case "Home":
- return currentUser ? : null;
+ return currentUser ? : null;
case "Projects":
return currentUser ? (
@@ -123,10 +284,30 @@ const MobileView = () => {
case "Tasks":
return currentUser ?
: null;
case "Activity":
- // ActivityLogView needs props. For now using placeholder or minimal props if possible.
-
-
- return
Activity Log (Coming Soon on Mobile)
;
+ return (
+
+ );
case "People":
return
{ }} />;
@@ -138,23 +319,6 @@ const MobileView = () => {
return ;
case "Settings":
return ;
- case "Profile":
- return (
-
-
-
-
- {currentUser?.displayName?.charAt(0)}
-
-
{currentUser?.displayName}
-
{currentUser?.email}
-
-
-
- Sign Out
-
-
- );
default:
return null;
}
@@ -191,7 +355,6 @@ const MobileView = () => {
photoURL: currentUser.photoURL ? getFullUrl(currentUser.photoURL) : undefined
} : null}
drawerContent={DrawerContent}
- onFabClick={() => navigate("/new-project")}
headerTitle={activeTab === 'Home' ? 'Dashboard' : activeTab}
>
{userMeError && currentUser && (
diff --git a/src/components/views/SettingsView.tsx b/src/components/views/SettingsView.tsx
index 532b71e..b585dbe 100644
--- a/src/components/views/SettingsView.tsx
+++ b/src/components/views/SettingsView.tsx
@@ -549,17 +549,19 @@ export default function SettingsView() {
};
return (
-
+
-
- My Profile
- Team
- Preferences
- Integrations
- Support
- Security
-
+
+
+ My Profile
+ Team
+ Preferences
+ Integrations
+ Support
+ Security
+
+
{}
diff --git a/src/components/views/mobile/MobileActivityLogView.tsx b/src/components/views/mobile/MobileActivityLogView.tsx
new file mode 100644
index 0000000..65005c6
--- /dev/null
+++ b/src/components/views/mobile/MobileActivityLogView.tsx
@@ -0,0 +1,356 @@
+import { useMemo, useState } from "react";
+import { formatDistanceToNow } from "date-fns";
+import { Activity, Clock3, Trash2 } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+
+interface MobileActivityLogViewProps {
+ activityLogs: any[];
+ tasks: any[];
+ users?: any[];
+ teamSessions?: any[];
+ ownedTeams?: any[];
+ myTeams?: any[];
+ currentUserId?: string;
+ currentUserProfile?: {
+ displayName?: string;
+ email?: string;
+ photoURL?: string;
+ } | null;
+ teamTasks?: any[];
+ elapsedTime: string;
+ onClearLogs: () => void;
+ onDeleteLog: (id: string) => void;
+}
+
+const normalizeStatus = (value: unknown) => String(value ?? "").toLowerCase().trim();
+
+const isCompleted = (task: any) => {
+ const status = normalizeStatus(task?.status);
+ return status.includes("complete") || status === "done";
+};
+
+const isInProgress = (task: any) => {
+ if (isCompleted(task)) return false;
+ const status = normalizeStatus(task?.status);
+ return status.includes("progress") || status === "active" || status === "in review";
+};
+
+const isOverdue = (task: any) => {
+ if (isCompleted(task)) return false;
+ const due = task?.dueDate || task?.deadline;
+ if (!due) return false;
+ return new Date(due).getTime() < Date.now();
+};
+
+const MobileActivityLogView = ({
+ activityLogs,
+ tasks,
+ users = [],
+ teamSessions = [],
+ ownedTeams = [],
+ myTeams = [],
+ currentUserId,
+ currentUserProfile,
+ teamTasks = [],
+ elapsedTime,
+ onClearLogs,
+ onDeleteLog,
+}: MobileActivityLogViewProps) => {
+ const totalTasks = tasks.length;
+ const completed = tasks.filter(isCompleted).length;
+ const inProgress = tasks.filter(isInProgress).length;
+ const overdue = tasks.filter(isOverdue).length;
+ const [expandedMemberId, setExpandedMemberId] = useState(null);
+ const elapsedSeconds = (() => {
+ const [h, m, s] = elapsedTime.split(":").map((part) => Number(part) || 0);
+ return h * 3600 + m * 60 + s;
+ })();
+
+ const ownedTeamIds = new Set(
+ (Array.isArray(ownedTeams) ? ownedTeams : [])
+ .map((team: any) => String(team?.id || team?._id || team?.teamId || ""))
+ .filter(Boolean)
+ );
+
+ const fallbackOwnedFromMyTeams = (Array.isArray(myTeams) ? myTeams : []).filter((team: any) => {
+ const owner = String(team?.ownerId || team?.ownerUid || team?.leaderId || team?.createdBy || "");
+ return Boolean(owner) && owner === currentUserId;
+ });
+
+ fallbackOwnedFromMyTeams.forEach((team: any) => {
+ const id = String(team?.id || team?._id || team?.teamId || "");
+ if (id) {
+ ownedTeamIds.add(id);
+ }
+ });
+
+ const isLeader = ownedTeamIds.size > 0;
+
+ const teamMemberStats = (() => {
+ if (!isLeader) {
+ return [];
+ }
+
+ const members = new Map();
+ const teamList = [...(ownedTeams || []), ...fallbackOwnedFromMyTeams];
+
+ teamList.forEach((team: any) => {
+ (team?.members || []).forEach((member: any) => {
+ const uid = String(typeof member === "string" ? member : member?.uid || member?.id || member?._id || "");
+ if (!uid) {
+ return;
+ }
+ const profileFromUsers = users.find((user: any) => user.uid === uid);
+ members.set(uid, {
+ uid,
+ displayName:
+ profileFromUsers?.displayName ||
+ (typeof member === "object" ? member?.displayName || member?.name : "") ||
+ uid,
+ email: profileFromUsers?.email || (typeof member === "object" ? member?.email : undefined),
+ photoURL: profileFromUsers?.photoURL || (typeof member === "object" ? member?.photoURL : undefined),
+ });
+ });
+ });
+
+ users.forEach((user: any) => {
+ const memberships = Array.isArray(user?.teamMemberships) ? user.teamMemberships.map(String) : [];
+ if (memberships.some((teamId: string) => ownedTeamIds.has(teamId))) {
+ members.set(user.uid, {
+ uid: user.uid,
+ displayName: user.displayName || user.email?.split("@")[0] || user.uid,
+ email: user.email,
+ photoURL: user.photoURL,
+ });
+ }
+ });
+
+ const totals = new Map();
+ (teamSessions || []).forEach((session: any) => {
+ const uid = String(session?.userId || "");
+ if (!uid || !members.has(uid)) {
+ return;
+ }
+ const secs = Number(session?.activeDuration || 0);
+ totals.set(uid, (totals.get(uid) || 0) + secs);
+ });
+
+ if (currentUserId && members.has(currentUserId)) {
+ totals.set(currentUserId, (totals.get(currentUserId) || 0) + elapsedSeconds);
+ }
+
+ return Array.from(members.values())
+ .map((member) => ({
+ ...member,
+ activeSeconds: totals.get(member.uid) || 0,
+ }))
+ .sort((a, b) => b.activeSeconds - a.activeSeconds);
+ })();
+
+ const toTimeLabel = (totalSeconds: number) => {
+ const safe = Math.max(0, totalSeconds);
+ const hours = Math.floor(safe / 3600);
+ const minutes = Math.floor((safe % 3600) / 60);
+ return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
+ };
+
+ const memberTaskStats = useMemo(() => {
+ const statsMap = new Map();
+
+ (Array.isArray(teamTasks) ? teamTasks : []).forEach((task: any) => {
+ const assignees = new Set();
+ if (task?.assignedTo) {
+ assignees.add(String(task.assignedTo));
+ }
+ if (Array.isArray(task?.assignedUserIds)) {
+ task.assignedUserIds.forEach((uid: any) => assignees.add(String(uid)));
+ }
+ if (assignees.size === 0) {
+ return;
+ }
+
+ assignees.forEach((uid) => {
+ const current = statsMap.get(uid) || { total: 0, completed: 0, inProgress: 0, overdue: 0 };
+ current.total += 1;
+ if (isCompleted(task)) {
+ current.completed += 1;
+ } else if (isInProgress(task)) {
+ current.inProgress += 1;
+ } else if (isOverdue(task)) {
+ current.overdue += 1;
+ }
+ statsMap.set(uid, current);
+ });
+ });
+
+ return statsMap;
+ }, [teamTasks]);
+
+ return (
+
+
+
+
+
+ Activity Summary
+
+
+
+
+
+
+
+ {currentUserProfile?.displayName?.charAt(0)?.toUpperCase() || "U"}
+
+
+
+
+ {currentUserProfile?.displayName || "Your Profile"}
+
+
+ {currentUserProfile?.email || currentUserId || ""}
+
+
+
+ Current session: {elapsedTime}
+
+
+
Total Tasks
+
{totalTasks}
+
+
+
Completed
+
{completed}
+
+
+
In Progress
+
{inProgress}
+
+
+
+
+
+
+ {isLeader && (
+
+
+ Team Member Activity
+
+
+ {teamMemberStats.length === 0 ? (
+
+ No team activity found yet.
+
+ ) : (
+ teamMemberStats.slice(0, 12).map((member) => {
+ const memberLabel = member.uid === currentUserId ? "You" : member.displayName;
+ const stats = memberTaskStats.get(member.uid) || {
+ total: 0,
+ completed: 0,
+ inProgress: 0,
+ overdue: 0,
+ };
+ const isExpanded = expandedMemberId === member.uid;
+
+ return (
+
+
setExpandedMemberId((prev) => (prev === member.uid ? null : member.uid))}
+ >
+
+
+ {member.displayName?.charAt(0)?.toUpperCase() || "U"}
+
+
+
{memberLabel}
+
+ {member.email || member.uid}
+
+
+ {toTimeLabel(member.activeSeconds)}
+
+
+ {isExpanded && (
+
+
+
Total Tasks
+
{stats.total}
+
+
+
Completed
+
{stats.completed}
+
+
+
In Progress
+
{stats.inProgress}
+
+
+
Overdue
+
{stats.overdue}
+
+
+ )}
+
+ );
+ })
+ )}
+
+
+ )}
+
+
+
+ Recent Activity
+ {activityLogs.length > 0 && (
+
+ Clear
+
+ )}
+
+
+ {activityLogs.length === 0 ? (
+ No activity yet.
+ ) : (
+ activityLogs.slice(0, 20).map((log, index) => {
+ const start = new Date(log.startTime);
+ const title = log.title || log.eventType || "Activity session";
+ const logKey =
+ log._id ||
+ `${log.startTime || "no-start"}-${log.eventType || title}-${log.userId || "no-user"}-${index}`;
+ return (
+
+
+
+
{title}
+
+ {formatDistanceToNow(start, { addSuffix: true })}
+
+
+ {log._id && (
+
onDeleteLog(log._id)}
+ >
+
+
+ )}
+
+ );
+ })
+ )}
+
+
+
+ );
+};
+
+export default MobileActivityLogView;
diff --git a/src/components/views/mobile/MobileDashboardView.tsx b/src/components/views/mobile/MobileDashboardView.tsx
new file mode 100644
index 0000000..dbbda7f
--- /dev/null
+++ b/src/components/views/mobile/MobileDashboardView.tsx
@@ -0,0 +1,135 @@
+import { eachDayOfInterval, formatISO } from "date-fns";
+import { Github, Users, GitFork } from "lucide-react";
+import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import {
+ ContributionGraph,
+ ContributionGraphBlock,
+ ContributionGraphCalendar,
+ ContributionGraphLegend,
+ ContributionGraphTotalCount,
+} from "@/components/kibo-ui/contribution-graph";
+import { useGitHubStats, useGitHubContributions } from "@/hooks/useGitHubData";
+import { useProjects } from "@/hooks/useProjects";
+
+const MobileDashboardView = ({ currentUser }: { currentUser: any }) => {
+ const { data: stats } = useGitHubStats(!!currentUser);
+ const { data: projects = [] } = useProjects();
+ const currentYear = new Date().getFullYear();
+ const { data: contributions = [] } = useGitHubContributions(currentYear, !!currentUser);
+
+ const contributionMap = contributions.reduce((acc, c) => {
+ acc[c.date] = c.count;
+ return acc;
+ }, {} as Record);
+
+ const maxCount = Math.max(...contributions.map((c) => c.count), 1);
+ const yearStart = new Date(currentYear, 0, 1);
+ const yearEnd = new Date(currentYear, 11, 31);
+ const days = eachDayOfInterval({ start: yearStart, end: yearEnd });
+ const graphData = days.map((date) => {
+ const dateStr = formatISO(date, { representation: "date" });
+ const count = contributionMap[dateStr] || 0;
+ const level = count === 0 ? 0 : Math.ceil((count / maxCount) * 4);
+ return { date: dateStr, count, level: Math.min(level, 4) };
+ });
+
+ const totalTasks = projects.reduce((sum, project: any) => {
+ const steps = project.steps || [];
+ return sum + steps.reduce((stepSum: number, step: any) => stepSum + (step.tasks?.length || 0), 0);
+ }, 0);
+
+ const completedTasks = projects.reduce((sum, project: any) => {
+ const steps = project.steps || [];
+ return (
+ sum +
+ steps.reduce(
+ (stepSum: number, step: any) =>
+ stepSum + (step.tasks?.filter((task: any) => task.status === "Completed" || task.status === "Done").length || 0),
+ 0
+ )
+ );
+ }, 0);
+
+ return (
+
+
+
+
+
+
+
+ {(stats?.login || currentUser?.displayName || "U").slice(0, 2).toUpperCase()}
+
+
+
+
{stats?.name || currentUser?.displayName || "Dashboard"}
+
+ {stats?.bio || currentUser?.email || "Welcome to your mobile dashboard"}
+
+
+
+
+ {stats?.followers ?? 0}
+
+
+
+ {stats?.following ?? 0}
+
+
+
+ {stats?.public_repos ?? projects.length}
+
+
+
+
+
+
+
+
+
+
+
+ Contributions
+
+
+
+
+
+
+ {({ activity, dayIndex, weekIndex }) => (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ {projects.length}
+ Projects
+
+
+
+
+ {totalTasks}
+ Tasks
+
+
+
+
+ {completedTasks}
+ Done
+
+
+
+
+ );
+};
+
+export default MobileDashboardView;
diff --git a/src/index.css b/src/index.css
index f8df537..b53a14a 100644
--- a/src/index.css
+++ b/src/index.css
@@ -175,6 +175,25 @@
.hero-gradient {
background: var(--gradient-hero);
}
+
+ /**
+ * Full-viewport canvas behind the resizable shell (not flat black).
+ * Main content panels use bg-transparent so this shows through.
+ */
+ .dashboard-backdrop {
+ background:
+ radial-gradient(ellipse 110% 75% at 50% -18%, rgb(147 51 234 / 0.28), transparent 58%),
+ radial-gradient(ellipse 85% 55% at 100% 25%, rgb(236 72 153 / 0.18), transparent 52%),
+ radial-gradient(ellipse 70% 50% at 0% 95%, rgb(59 130 246 / 0.2), transparent 55%),
+ linear-gradient(165deg, hsl(222 47% 12%) 0%, hsl(262 35% 10%) 42%, hsl(222 47% 7%) 100%);
+ }
+
+ .light .dashboard-backdrop {
+ background:
+ radial-gradient(ellipse 110% 75% at 50% -18%, rgb(147 51 234 / 0.12), transparent 58%),
+ radial-gradient(ellipse 85% 55% at 100% 25%, rgb(236 72 153 / 0.1), transparent 52%),
+ linear-gradient(165deg, hsl(210 50% 98%) 0%, hsl(260 40% 96%) 100%);
+ }
}
@layer utilities {
diff --git a/src/lib/firebase.ts b/src/lib/firebase.ts
index 2d840ce..d2112a2 100644
--- a/src/lib/firebase.ts
+++ b/src/lib/firebase.ts
@@ -34,10 +34,6 @@ if (typeof window !== "undefined" && isValidRecaptchaSiteKey(recaptchaSiteKey))
provider: new ReCaptchaV3Provider(recaptchaSiteKey),
isTokenAutoRefreshEnabled: true,
});
-} else if (import.meta.env.DEV && typeof window !== "undefined") {
- console.info(
- "[Firebase] App Check skipped: set VITE_RECAPTCHA_SITE_KEY to your reCAPTCHA v3 site key to enable.",
- );
}
export const auth = getAuth(app);
diff --git a/src/mobile/pages/DashboardMobile.tsx b/src/mobile/pages/DashboardMobile.tsx
new file mode 100644
index 0000000..15da5bc
--- /dev/null
+++ b/src/mobile/pages/DashboardMobile.tsx
@@ -0,0 +1,5 @@
+import Dashboard from "@/pages/Dashboard";
+
+const DashboardMobile = () => ;
+
+export default DashboardMobile;
diff --git a/src/mobile/pages/IndexMobile.tsx b/src/mobile/pages/IndexMobile.tsx
new file mode 100644
index 0000000..56db670
--- /dev/null
+++ b/src/mobile/pages/IndexMobile.tsx
@@ -0,0 +1,86 @@
+import { Link } from "react-router-dom";
+import { ArrowRight, CheckSquare, MessageSquare, CalendarDays, FolderKanban } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+
+const features = [
+ {
+ title: "Workspace",
+ description: "Track projects and architecture in one place.",
+ icon: FolderKanban,
+ },
+ {
+ title: "Tasks",
+ description: "Assign and monitor task progress quickly.",
+ icon: CheckSquare,
+ },
+ {
+ title: "Chat & Meet",
+ description: "Collaborate with your team in real time.",
+ icon: MessageSquare,
+ },
+ {
+ title: "Calendar",
+ description: "Stay aligned with deadlines and meetings.",
+ icon: CalendarDays,
+ },
+];
+
+const IndexMobile = () => {
+ return (
+
+
+
+
+
+
Zync
+
+
+ Login
+
+
+
+
+
+ Build Faster With Your Team
+
+
+ Planning, tasks, chat, notes, and progress in one mobile-ready workspace.
+
+
+
+
+ Get Started
+
+
+
+
+ Open Dashboard
+
+
+
+
+
+ {features.map((feature) => {
+ const Icon = feature.icon;
+ return (
+
+
+
+
+ {feature.title}
+
+
+
+ {feature.description}
+
+
+ );
+ })}
+
+
+
+ );
+};
+
+export default IndexMobile;
diff --git a/src/mobile/pages/LoginMobile.tsx b/src/mobile/pages/LoginMobile.tsx
new file mode 100644
index 0000000..4e65187
--- /dev/null
+++ b/src/mobile/pages/LoginMobile.tsx
@@ -0,0 +1,5 @@
+import Login from "@/pages/Login";
+
+const LoginMobile = () => ;
+
+export default LoginMobile;
diff --git a/src/mobile/pages/NewProjectMobile.tsx b/src/mobile/pages/NewProjectMobile.tsx
new file mode 100644
index 0000000..ea38a6e
--- /dev/null
+++ b/src/mobile/pages/NewProjectMobile.tsx
@@ -0,0 +1,5 @@
+import NewProject from "@/pages/NewProject";
+
+const NewProjectMobile = () => ;
+
+export default NewProjectMobile;
diff --git a/src/mobile/pages/NotFoundMobile.tsx b/src/mobile/pages/NotFoundMobile.tsx
new file mode 100644
index 0000000..deab440
--- /dev/null
+++ b/src/mobile/pages/NotFoundMobile.tsx
@@ -0,0 +1,5 @@
+import NotFound from "@/pages/NotFound";
+
+const NotFoundMobile = () => ;
+
+export default NotFoundMobile;
diff --git a/src/mobile/pages/PrivacyMobile.tsx b/src/mobile/pages/PrivacyMobile.tsx
new file mode 100644
index 0000000..9f67f60
--- /dev/null
+++ b/src/mobile/pages/PrivacyMobile.tsx
@@ -0,0 +1,5 @@
+import Privacy from "@/pages/Privacy";
+
+const PrivacyMobile = () => ;
+
+export default PrivacyMobile;
diff --git a/src/mobile/pages/PrivacyPolicyMobile.tsx b/src/mobile/pages/PrivacyPolicyMobile.tsx
new file mode 100644
index 0000000..e65b609
--- /dev/null
+++ b/src/mobile/pages/PrivacyPolicyMobile.tsx
@@ -0,0 +1,5 @@
+import PrivacyPolicy from "@/pages/PrivacyPolicy";
+
+const PrivacyPolicyMobile = () => ;
+
+export default PrivacyPolicyMobile;
diff --git a/src/mobile/pages/ProjectDetailsMobile.tsx b/src/mobile/pages/ProjectDetailsMobile.tsx
new file mode 100644
index 0000000..919582d
--- /dev/null
+++ b/src/mobile/pages/ProjectDetailsMobile.tsx
@@ -0,0 +1,5 @@
+import ProjectDetails from "@/pages/ProjectDetails";
+
+const ProjectDetailsMobile = () => ;
+
+export default ProjectDetailsMobile;
diff --git a/src/mobile/pages/SignupMobile.tsx b/src/mobile/pages/SignupMobile.tsx
new file mode 100644
index 0000000..ca3ad8a
--- /dev/null
+++ b/src/mobile/pages/SignupMobile.tsx
@@ -0,0 +1,5 @@
+import Signup from "@/pages/Signup";
+
+const SignupMobile = () => ;
+
+export default SignupMobile;
diff --git a/src/mobile/pages/TermsMobile.tsx b/src/mobile/pages/TermsMobile.tsx
new file mode 100644
index 0000000..7c793cb
--- /dev/null
+++ b/src/mobile/pages/TermsMobile.tsx
@@ -0,0 +1,5 @@
+import Terms from "@/pages/Terms";
+
+const TermsMobile = () => ;
+
+export default TermsMobile;
diff --git a/src/mobile/pages/WelcomeToZyncMobile.tsx b/src/mobile/pages/WelcomeToZyncMobile.tsx
new file mode 100644
index 0000000..1f439f5
--- /dev/null
+++ b/src/mobile/pages/WelcomeToZyncMobile.tsx
@@ -0,0 +1,5 @@
+import WelcomeToZync from "@/pages/WelcomeToZync";
+
+const WelcomeToZyncMobile = () => ;
+
+export default WelcomeToZyncMobile;