Skip to content
Open
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
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,21 @@
# Todo
# Todo

Netlify: carolinas-todo-app.netlify.app

A sleek, responsive **To-Do Application** built with **React** and **Vite**, designed for a seamless user experience across mobile and desktop devices. The app allows users to manage their daily tasks efficiently with a clean, modern interface powered by **Styled Components**.

## 🚀 Key Features

* **Dynamic State Management**: Utilizes **Zustand** for centralized global state, ensuring efficient task updates and real-time tracking of completed items.
* **Interactive UI**: Includes high-fidelity **Lottie animations** (confetti bursts) that trigger upon task completion to provide delightful user feedback.
* **Responsive Design**: Features a mobile-first approach with custom media queries and dynamic viewport units (`100dvh`) to ensure perfect layout rendering on both iOS Safari and desktop browsers.
* **Theming & Styling**: Implements a consistent design system using a custom **ThemeProvider**, featuring a soft peach and teal color palette, responsive typography, and specialized global resets.
* **Smart Date Formatting**: Integrates **Moment.js** to display relative timestamps (e.g., "5 minutes ago") for when tasks were created.

## 🛠️ Tech Stack

* **Frontend Library**: React (Vite)
* **State Management**: Zustand
* **Styling**: Styled Components
* **Animations**: Lottie-React
* **Utilities**: Moment.js
52 changes: 38 additions & 14 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,16 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="./vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Todo</title>
</head>
<body>
<div id="root"></div>
<script
type="module"
src="./src/main.jsx">
</script>
</body>
</html>

<head>
<meta charset="UTF-8" />
<link
rel="preconnect"
href="https://fonts.googleapis.com"
>
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossorigin
>
<link
href="https://fonts.googleapis.com/css2?family=Sniglet:wght@400;800&display=swap"
rel="stylesheet"
>
<link
rel="icon"
type="image/svg+xml"
href="/icons&images/sun.png"
/>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<title>To-Do app by Carolina</title>
</head>

<body>
<div id="root"></div>
<script
type="module"
src="./src/main.jsx"
>
</script>
</body>

</html>
11 changes: 10 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,17 @@
"preview": "vite preview"
},
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@mui/icons-material": "^7.3.7",
"@mui/material": "^7.3.7",
"@mui/styled-engine-sc": "^7.3.7",
"lottie-react": "^2.4.1",
"moment": "^2.30.1",
"react": "^19.0.0",
"react-dom": "^19.0.0"
"react-dom": "^19.0.0",
"styled-components": "^6.3.6",
"zustand": "^5.0.10"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
Expand Down
8 changes: 8 additions & 0 deletions public/icons&images/snake.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/icons&images/sun.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/icons&images/x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 35 additions & 1 deletion src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,39 @@
import styled from "styled-components"
import { SubmitBar } from "./components/submit-bar/SubmitBar"
import { SubmittedList } from "./components/submitted-todos/SubmittedList"
import { EmptyStateCard } from "./components/empty-state/EmptyStateCard"
import { useToDoStore } from "./store/ToDoStore"

const StyledMainWrapper = styled.div`
overflow-x: hidden;
min-height: 100dvh;
position: relative;
`

const StyledHeading = styled.h1`
text-align: center;
font-size: ${({ theme }) => theme.fontSizes.h1};
margin: 30px 0 20px 0;
font-weight: ${({ theme }) => theme.fontWeights.bold};
`

const StyledCardContainer = styled.div`
margin-top: 30px;
`

export const App = () => {
const todos = useToDoStore(state => state.todos)
return (
<h1>React Boilerplate</h1>
<StyledMainWrapper>
<StyledHeading>To-do app</StyledHeading>
<SubmitBar />
<StyledCardContainer>
{todos.length === 0 ? (
<EmptyStateCard />
) : (
<SubmittedList />
)}
</StyledCardContainer>
</StyledMainWrapper>
)
}
36 changes: 36 additions & 0 deletions src/GlobalStyle.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { createGlobalStyle } from "styled-components"

export const GlobalStyle = createGlobalStyle`

* {
box-sizing: border-box;
}

body {
margin: 0;
padding: 0;
font-family: 'Sniglet', sans-serif;
color: ${({ theme }) => theme.colors.primary};
background-color: ${({ theme }) => theme.colors.background};

background-image: url("/icons&images/snake.svg");
background-repeat: no-repeat;
background-attachment: fixed;
background-position: 50% 50%;
background-size: 280%;

@media (min-width: ${({ theme }) => theme.breakpoints.tablet}) {
background-size: 180%;
}

@media (min-width: ${({ theme }) => theme.breakpoints.desktop}) {
background-size: 150%;
background-position: 50% 40%;
}
}

input, button, textarea, select {
font-family: inherit;
}

`
36 changes: 36 additions & 0 deletions src/Theme.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export const Theme = {

colors: {
primary: '#15616d', // Deep Teal (Buttons, borders, icons)
secondary: '#ffecd1', // Light Apricot (Card backgrounds)
background: '#ffefe7', // Soft Peach (Body background)
text: '#001524', // Dark Navy (Main text)
white: '#ffffff',
transparent: 'transparent',
},

fontSizes: {
sm: '14px',
md: '16px',
lg: '18px',
xl: '20px',
xxl: '24px',
xxxl: '28px',
h1: '35px',
},

fontWeights: {
normal: 400,
bold: 800,
},

shadows: {
card: '0 4px 6px rgba(0, 0, 0, 0.1)',
},

breakpoints: {
mobile: '576px',
tablet: '768px',
desktop: '1024px',
}
}
1 change: 1 addition & 0 deletions src/assets/ConfettiDay.json

Large diffs are not rendered by default.

66 changes: 66 additions & 0 deletions src/components/empty-state/EmptyStateCard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import styled from 'styled-components'

const StyledCard = styled.div`
width: 75%;
background: ${({ theme }) => theme.colors.secondary};
margin: 30px auto 0 auto;
padding: 20px;
text-align: center;
border-radius: 6px;
box-shadow: ${({ theme }) => theme.shadows.card};

@media (min-width: ${({ theme }) => theme.breakpoints.tablet}) {
width: 60%;
}

@media (min-width: ${({ theme }) => theme.breakpoints.desktop}) {
width: 40%;
}
`
const StyledHeading = styled.h2`
font-size: ${({ theme }) => theme.fontSizes.xl};

@media (min-width: ${({ theme }) => theme.breakpoints.tablet}) {
font-size: ${({ theme }) => theme.fontSizes.xxl};
}

@media (min-width: ${({ theme }) => theme.breakpoints.desktop}) {
font-size: ${({ theme }) => theme.fontSizes.xxxl};
}
`

const StyledText = styled.p`
font-size: ${({ theme }) => theme.fontSizes.md};

@media (min-width: ${({ theme }) => theme.breakpoints.tablet}) {
font-size: ${({ theme }) => theme.fontSizes.lg};
}

@media (min-width: ${({ theme }) => theme.breakpoints.desktop}) {
font-size: ${({ theme }) => theme.fontSizes.xl};
}
`
const StyledImage = styled.img`
width: 50%;
height: auto;
display: flex;
margin: 30px auto 0 auto;

@media (min-width: ${({ theme }) => theme.breakpoints.tablet}) {
width: 40%;
}

@media (min-width: ${({ theme }) => theme.breakpoints.desktop}) {
width: 30%;
}
`

export const EmptyStateCard = () => {
return (
<StyledCard>
<StyledHeading>No tasks yet</StyledHeading>
<StyledImage src="/icons&images/sun.png" alt="happy sun illustration" />
<StyledText>The day is yours to conquer! <br /> What do you want to achieve today?</StyledText>
</StyledCard>
)
}
76 changes: 76 additions & 0 deletions src/components/submit-bar/SubmitBar.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import styled from "styled-components"
import { SubmitButton } from "./SubmitButton"
import { useState } from "react"
import { useToDoStore } from "../../store/ToDoStore"


const StyledBar = styled.div`
width: 90%;
height: 48px;
border: 2px ${({ theme }) => theme.colors.primary} solid;
border-radius: 6px;
background: ${({ theme }) => theme.colors.secondary};
margin: 20px auto;
overflow: hidden;
display: flex;

@media (min-width: ${({ theme }) => theme.breakpoints.tablet}) {
width: 65%;
}

@media (min-width: ${({ theme }) => theme.breakpoints.desktop}) {
width: 47%;
}
`
const StyledForm = styled.form`
display: flex;
justify-content: space-between;
width: 100%;
`
const StyledLabel = styled.label`
align-content: center;
`

const StyledInput = styled.input`
flex-grow: 1;
border: none;
background: ${({ theme }) => theme.colors.transparent};
font-family: 'Sniglet', sans-serif;
font-size: ${({ theme }) => theme.fontSizes.md};
margin-left: 10px;
outline: none;

@media (min-width: ${({ theme }) => theme.breakpoints.tablet}) {
font-size: ${({ theme }) => theme.fontSizes.lg};
}
`

export const SubmitBar = () => {
const [text, setText] = useState("")
const addTodo = useToDoStore(state => state.addTodo)

const handleSubmit = (e) => {
e.preventDefault()
if (!text.trim()) return
addTodo(text)
setText("")
}

return (
<StyledBar>
<StyledForm onSubmit={handleSubmit}>
<StyledLabel>
<StyledInput
type="text"
name="todo"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Type an new task ..."
aria-label="Type a new task"
/>
</StyledLabel>
<SubmitButton />
</StyledForm>
</StyledBar>
)
}
22 changes: 22 additions & 0 deletions src/components/submit-bar/SubmitButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import styled from 'styled-components'

const StyledButton = styled.button`
background: ${({ theme }) => theme.colors.primary};
color: ${({ theme }) => theme.colors.white};
border: none;
height: 100%;
padding: 0 10px;
font-family: 'Sniglet', sans-serif;
font-size: ${({ theme }) => theme.fontSizes.md};
align-items: center;

@media (min-width: ${({ theme }) => theme.breakpoints.tablet}) {
font-size: ${({ theme }) => theme.fontSizes.lg};
}
`

export const SubmitButton = () => {
return (
<StyledButton type="submit">Add</StyledButton>
)
}
Loading