mirror of
https://github.com/nagaoo0/HabbitGrid.git
synced 2026-04-19 15:23:16 +00:00
Compare commits
4 Commits
39f7bbd96f
...
4ae00cac87
| Author | SHA1 | Date | |
|---|---|---|---|
| 4ae00cac87 | |||
| b7388c2ccc | |||
| c89c667304 | |||
| 831edbef49 |
35
index.html
35
index.html
@@ -5,6 +5,41 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Habit Tracker App</title>
|
||||
<meta name="description" content="Track your habits and visualize your progress with HabitGrid." />
|
||||
<meta name="keywords" content="habit tracker, productivity, goals, progress, HabitGrid, daily habits, motivation" />
|
||||
<meta name="author" content="Mihajlo Ciric" />
|
||||
<meta name="theme-color" content="#2563eb" />
|
||||
<!-- Open Graph Meta Tags -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebApplication",
|
||||
"name": "HabitGrid",
|
||||
"url": "https://myhabitgrid.com",
|
||||
"description": "HabitGrid is a habit tracker app that helps users build and maintain daily habits using a GitHub-style contribution grid. Visualize your progress, build streaks, and stay motivated.",
|
||||
"applicationCategory": "Productivity",
|
||||
"operatingSystem": "All",
|
||||
"creator": {
|
||||
"@type": "Person",
|
||||
"name": "Mihajlo Ciric"
|
||||
},
|
||||
"keywords": ["habit tracker", "productivity", "goals", "progress", "daily habits", "motivation", "HabitGrid"],
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0",
|
||||
"priceCurrency": "USD"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<meta property="og:title" content="Habit Tracker App" />
|
||||
<meta property="og:description" content="Track your habits and visualize your progress with HabitGrid." />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://myhabitgrid.com" />
|
||||
<meta property="og:image" content="/assets/fav.png" />
|
||||
<!-- Twitter Card Meta Tags -->
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="Habit Tracker App" />
|
||||
<meta name="twitter:description" content="Track your habits and visualize your progress with HabitGrid." />
|
||||
<meta name="twitter:image" content="/assets/fav.png" />
|
||||
<link rel="icon" type="image/png" href="/assets/fav.png" />
|
||||
<link rel="stylesheet" href="/src/index.css" />
|
||||
</head>
|
||||
|
||||
3
public/robots.txt
Normal file
3
public/robots.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
Sitemap: https://myhabitgrid.com/sitemap.xml
|
||||
16
public/sitemap.xml
Normal file
16
public/sitemap.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://yourdomain.com/</loc>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://yourdomain.com/add</loc>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://yourdomain.com/settings</loc>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://yourdomain.com/login-providers</loc>
|
||||
</url>
|
||||
<!-- For dynamic routes like /habit/:id and /edit/:id, add actual URLs if you have them -->
|
||||
</urlset>
|
||||
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
|
||||
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd';
|
||||
// ...existing code...
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { Plus, Settings, TrendingUp, Flame, Calendar, Moon, Sun } from 'lucide-react';
|
||||
import { Plus, Settings, TrendingUp, Flame, Calendar, Moon, Sun, Star } from 'lucide-react';
|
||||
import { Button } from '../components/ui/button';
|
||||
import { useToast } from '../components/ui/use-toast';
|
||||
import HabitCard from '../components/HabitCard';
|
||||
@@ -188,67 +188,83 @@ const HomePage = () => {
|
||||
|
||||
let newHabits = [...habits];
|
||||
|
||||
// If dropping into uncategorized, always unset category
|
||||
// Helper to update local storage and UI instantly
|
||||
const updateLocalOrder = (habitsArr) => {
|
||||
localStorage.setItem('habitgrid_data', JSON.stringify(habitsArr));
|
||||
setHabits(habitsArr);
|
||||
};
|
||||
|
||||
// Collect async remote updates to fire after local update
|
||||
let remoteUpdates = [];
|
||||
|
||||
if (destination.droppableId === 'uncategorized') {
|
||||
let items, removed;
|
||||
if (source.droppableId === 'uncategorized') {
|
||||
// Reorder within uncategorized
|
||||
items = Array.from(uncategorized);
|
||||
[removed] = items.splice(source.index, 1);
|
||||
} else {
|
||||
// Move from category to uncategorized
|
||||
items = Array.from(uncategorized);
|
||||
const sourceItems = Array.from(grouped[source.droppableId]);
|
||||
[removed] = sourceItems.splice(source.index, 1);
|
||||
removed.category = '';
|
||||
grouped[source.droppableId] = sourceItems;
|
||||
}
|
||||
// Always set category to ''
|
||||
removed.category = '';
|
||||
items.splice(destination.index, 0, removed);
|
||||
items.forEach((h, i) => updateHabit(h.id, { sortOrder: i, category: '' }));
|
||||
items.forEach((h, i) => {
|
||||
h.sortOrder = i;
|
||||
h.category = '';
|
||||
remoteUpdates.push(updateHabit(h.id, { sortOrder: i, category: '' }));
|
||||
});
|
||||
newHabits = [
|
||||
...items,
|
||||
...Object.values(grouped).flat()
|
||||
];
|
||||
updateLocalOrder(newHabits);
|
||||
} else if (source.droppableId === 'uncategorized' && grouped[destination.droppableId]) {
|
||||
// Move from uncategorized to category
|
||||
const items = Array.from(uncategorized);
|
||||
const [removed] = items.splice(source.index, 1);
|
||||
removed.category = destination.droppableId;
|
||||
const destItems = Array.from(grouped[destination.droppableId] || []);
|
||||
destItems.splice(destination.index, 0, removed);
|
||||
destItems.forEach((h, i) => updateHabit(h.id, { sortOrder: i, category: h.category }));
|
||||
destItems.forEach((h, i) => {
|
||||
h.sortOrder = i;
|
||||
remoteUpdates.push(updateHabit(h.id, { sortOrder: i, category: h.category }));
|
||||
});
|
||||
newHabits = [
|
||||
...items,
|
||||
...Object.values({ ...grouped, [destination.droppableId]: destItems }).flat()
|
||||
];
|
||||
updateLocalOrder(newHabits);
|
||||
} else if (grouped[source.droppableId] && grouped[destination.droppableId]) {
|
||||
// Move within or between categories
|
||||
const sourceItems = Array.from(grouped[source.droppableId]);
|
||||
const [removed] = sourceItems.splice(source.index, 1);
|
||||
if (source.droppableId === destination.droppableId) {
|
||||
// Reorder within same category
|
||||
sourceItems.splice(destination.index, 0, removed);
|
||||
sourceItems.forEach((h, i) => updateHabit(h.id, { sortOrder: i, category: h.category }));
|
||||
sourceItems.forEach((h, i) => {
|
||||
h.sortOrder = i;
|
||||
remoteUpdates.push(updateHabit(h.id, { sortOrder: i, category: h.category }));
|
||||
});
|
||||
grouped[source.droppableId] = sourceItems;
|
||||
} else {
|
||||
// Move to another category
|
||||
const destItems = Array.from(grouped[destination.droppableId] || []);
|
||||
removed.category = destination.droppableId;
|
||||
destItems.splice(destination.index, 0, removed);
|
||||
destItems.forEach((h, i) => updateHabit(h.id, { sortOrder: i, category: h.category }));
|
||||
destItems.forEach((h, i) => {
|
||||
h.sortOrder = i;
|
||||
remoteUpdates.push(updateHabit(h.id, { sortOrder: i, category: h.category }));
|
||||
});
|
||||
grouped[source.droppableId] = sourceItems;
|
||||
grouped[destination.droppableId] = destItems;
|
||||
}
|
||||
// Flatten
|
||||
newHabits = [
|
||||
...uncategorized,
|
||||
...Object.values(grouped).flat()
|
||||
];
|
||||
updateLocalOrder(newHabits);
|
||||
}
|
||||
// Force immediate UI update after all updates
|
||||
loadHabits();
|
||||
// Fire remote updates async, do not block UI
|
||||
Promise.allSettled(remoteUpdates);
|
||||
}}
|
||||
>
|
||||
<div className="space-y-6">
|
||||
@@ -425,6 +441,34 @@ const HomePage = () => {
|
||||
</Button>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ delay: 0.3 }}
|
||||
className="fixed bottom-6 left-6"
|
||||
>
|
||||
<Button
|
||||
onClick={() => window.open("https://github.com/nagaoo0/HabitGrid", "_blank")}
|
||||
size="lg"
|
||||
className="rounded-full shadow-lg hover:shadow-xl transition-shadow bg-gray-600 hover:bg-gray-700 text-white"
|
||||
>
|
||||
<a
|
||||
href="https://github.com/nagaoo0/HabitGrid"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center space-x-2"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white" className="w-7 h-7 text-slate-700 dark:text-slate-200">
|
||||
<path d="M12 2C6.477 2 2 6.484 2 12.021c0 4.428 2.865 8.186 6.839 9.525.5.092.682-.217.682-.483 0-.237-.009-.868-.014-1.703-2.782.605-3.369-1.342-3.369-1.342-.454-1.155-1.11-1.463-1.11-1.463-.908-.62.069-.608.069-.608 1.004.07 1.532 1.032 1.532 1.032.892 1.53 2.341 1.088 2.91.832.091-.646.35-1.088.636-1.34-2.221-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.254-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.025A9.564 9.564 0 0 1 12 6.844c.85.004 1.705.115 2.504.337 1.909-1.295 2.748-1.025 2.748-1.025.546 1.378.202 2.396.1 2.65.64.7 1.028 1.595 1.028 2.688 0 3.847-2.337 4.695-4.566 4.944.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.579.688.481C19.138 20.204 22 16.447 22 12.021 22 6.484 17.523 2 12 2z" />
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
</Button>
|
||||
|
||||
</motion.div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user