├── src
├── vite-env.d.ts
├── layouts
│ ├── index.ts
│ ├── DashboardLayout.tsx
│ └── AuthLayout.tsx
├── components
│ ├── index.ts
│ ├── shared
│ │ ├── sidemenu
│ │ │ ├── SideMenu.css
│ │ │ ├── SideMenuItem.tsx
│ │ │ └── SideMenu.tsx
│ │ └── cards
│ │ │ └── WhiteCard.tsx
│ └── jira
│ │ └── JiraTasks.tsx
├── pages
│ ├── index.ts
│ ├── 02-objects
│ │ └── JiraPage.tsx
│ ├── 01-basic
│ │ ├── BearPage.tsx
│ │ └── PersonPage.tsx
│ ├── dashboard
│ │ └── DashboardPage.tsx
│ ├── auth
│ │ └── LoginPage.tsx
│ └── 03-slices
│ │ └── WeddingInvitationPage.tsx
├── Root.tsx
├── main.tsx
├── index.css
├── router
│ └── router.tsx
└── assets
│ └── react.svg
├── public
├── screenshot.png
└── vite.svg
├── postcss.config.js
├── vite.config.ts
├── tailwind.config.js
├── tsconfig.node.json
├── .gitignore
├── index.html
├── README.md
├── .eslintrc.cjs
├── tsconfig.json
└── package.json
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/layouts/index.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | export * from './AuthLayout';
4 | export * from './DashboardLayout';
--------------------------------------------------------------------------------
/public/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Klerith/zustand-mini-curso/HEAD/public/screenshot.png
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/src/components/index.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | export * from './jira/JiraTasks';
4 |
5 |
6 | export * from './shared/sidemenu/SideMenu';
7 | export * from './shared/cards/WhiteCard';
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | content: [
4 | "./index.html",
5 | "./src/**/*.{js,ts,jsx,tsx}",
6 | ],
7 | theme: {
8 | extend: {},
9 | },
10 | plugins: [],
11 | }
12 |
13 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "include": ["vite.config.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/src/components/shared/sidemenu/SideMenu.css:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | #nav a {
6 | @apply w-full px-2 inline-flex space-x-2 items-center border-b border-slate-700 py-3 hover:bg-white/5 transition ease-linear duration-150;
7 | }
8 |
9 | #nav a.active {
10 | @apply bg-blue-800;
11 | }
--------------------------------------------------------------------------------
/src/pages/index.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | export * from './dashboard/DashboardPage';
4 | export * from './01-basic/BearPage';
5 | export * from './01-basic/PersonPage';
6 | export * from './02-objects/JiraPage';
7 | export * from './03-slices/WeddingInvitationPage';
8 |
9 |
10 | export * from './auth/LoginPage';
--------------------------------------------------------------------------------
/src/Root.tsx:
--------------------------------------------------------------------------------
1 | import { Navigate, Outlet, useLocation } from 'react-router-dom';
2 |
3 |
4 | export const Root = () => {
5 |
6 | const { pathname } = useLocation();
7 |
8 | if (pathname === '/') {
9 | return ;
10 | }
11 |
12 | return (
13 |
14 |
15 |
16 | )
17 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 |
4 | import './index.css'
5 | import { RouterProvider } from 'react-router-dom';
6 | import { router } from './router/router.tsx';
7 |
8 | ReactDOM.createRoot(document.getElementById('root')!).render(
9 |
10 |
11 | ,
12 | )
13 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React + TypeScript + Vite + Zustand + TailwindCSS + ReactRouterDom
2 |
3 | Este es un cascarón de proyecto, siéntete libre de usarlo para tus proyectos.
4 |
5 |
6 |
7 |
8 |
9 | ## Instalar
10 |
11 | 1. Clonar proyecto
12 | 2. Instalar dependencias ```npm install```
13 | 3. Correr en desarrollo ```npm run dev```
14 |
15 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:@typescript-eslint/recommended',
7 | 'plugin:react-hooks/recommended',
8 | ],
9 | ignorePatterns: ['dist', '.eslintrc.cjs'],
10 | parser: '@typescript-eslint/parser',
11 | plugins: ['react-refresh'],
12 | rules: {
13 | 'react-refresh/only-export-components': [
14 | 'warn',
15 | { allowConstantExport: true },
16 | ],
17 | },
18 | }
19 |
--------------------------------------------------------------------------------
/src/layouts/DashboardLayout.tsx:
--------------------------------------------------------------------------------
1 | import { SideMenu } from '../components';
2 | import { Outlet } from 'react-router-dom';
3 |
4 | export const DashboardLayout = () => {
5 | return (
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | );
18 | };
--------------------------------------------------------------------------------
/src/components/shared/cards/WhiteCard.tsx:
--------------------------------------------------------------------------------
1 | import classNames from 'classnames';
2 |
3 | interface Props {
4 | children?: React.ReactNode;
5 | centered?: boolean;
6 | className?: string;
7 | }
8 |
9 |
10 | export const WhiteCard = ( { children, centered, className }: Props ) => {
11 | return (
12 |
17 | { children }
18 |
19 | );
20 | };
--------------------------------------------------------------------------------
/src/pages/02-objects/JiraPage.tsx:
--------------------------------------------------------------------------------
1 | import { JiraTasks } from '../../components';
2 |
3 | export const JiraPage = () => {
4 | return (
5 | <>
6 | Tareas
7 | Manejo de estado con objectos de Zustand
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | >
25 | );
26 | };
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 | "jsx": "react-jsx",
16 |
17 | /* Linting */
18 | "strict": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "noFallthroughCasesInSwitch": true
22 | },
23 | "include": ["src"],
24 | "references": [{ "path": "./tsconfig.node.json" }]
25 | }
26 |
--------------------------------------------------------------------------------
/src/layouts/AuthLayout.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { Outlet } from 'react-router-dom';
3 |
4 | export const AuthLayout = () => {
5 | return (
6 |
7 |
8 |
Zustand
9 | {/*

*/}
12 |
13 |
14 |
15 |
16 |
17 | );
18 | };
--------------------------------------------------------------------------------
/src/components/shared/sidemenu/SideMenuItem.tsx:
--------------------------------------------------------------------------------
1 |
2 | import type { IconType } from 'react-icons';
3 | import { NavLink } from 'react-router-dom';
4 |
5 | interface Props {
6 | href: string;
7 | Icon: IconType;
8 | title: string;
9 | subTitle: string
10 | }
11 |
12 |
13 | export const SideMenuItem = ({ href, Icon, title, subTitle }: Props) => {
14 | return (
15 |
20 |
21 |
22 |
23 |
24 | { title }
25 | { subTitle }
26 |
27 |
28 | );
29 | }
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 |
6 | h1 {
7 | @apply text-3xl font-bold text-gray-800;
8 | }
9 |
10 | h2 {
11 | @apply text-2xl font-semibold text-gray-800;
12 | }
13 |
14 | hr {
15 | @apply my-1 border-gray-300 border mb-3;
16 | }
17 |
18 | button {
19 | @apply transition-all hover:bg-indigo-800 bg-indigo-600 px-8 py-2 rounded-3xl text-gray-100 font-semibold uppercase tracking-wide shadow-md;
20 | }
21 |
22 | input:not(.swal2-input)[type=text],input:not(.swal2-input)[type=password], input:not(.swal2-input)[type=email], input:not(.swal2-input)[type=number], input:not(.swal2-input)[type=date], input:not(.swal2-input)[type=time] {
23 | @apply w-full border border-gray-300 rounded-md py-2 px-3 focus:outline-none focus:border-blue-500;
24 | }
25 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "zustand-dash",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "classnames": "^2.3.2",
14 | "react": "^18.2.0",
15 | "react-dom": "^18.2.0",
16 | "react-icons": "^4.11.0",
17 | "react-router-dom": "^6.16.0"
18 | },
19 | "devDependencies": {
20 | "@types/react": "^18.2.15",
21 | "@types/react-dom": "^18.2.7",
22 | "@typescript-eslint/eslint-plugin": "^6.0.0",
23 | "@typescript-eslint/parser": "^6.0.0",
24 | "@vitejs/plugin-react": "^4.0.3",
25 | "autoprefixer": "^10.4.16",
26 | "eslint": "^8.45.0",
27 | "eslint-plugin-react-hooks": "^4.6.0",
28 | "eslint-plugin-react-refresh": "^0.4.3",
29 | "postcss": "^8.4.31",
30 | "tailwindcss": "^3.3.3",
31 | "typescript": "^5.0.2",
32 | "vite": "^4.4.5"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/pages/01-basic/BearPage.tsx:
--------------------------------------------------------------------------------
1 | import { WhiteCard } from '../../components';
2 |
3 | export const BearPage = () => {
4 | return (
5 | <>
6 | Contador de Osos
7 | Manejo de estado simple de Zustand
8 |
9 |
10 |
11 |
12 |
13 | Osos Negros
14 |
15 |
16 |
17 | 0
18 |
19 |
20 |
21 |
22 |
23 |
24 | Osos Polares
25 |
26 |
27 |
28 | 0
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | Osos Pandas
37 |
38 |
39 |
40 | 0
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | >
52 | );
53 | };
--------------------------------------------------------------------------------
/src/router/router.tsx:
--------------------------------------------------------------------------------
1 | import { createBrowserRouter } from 'react-router-dom';
2 |
3 | import { Root } from '../Root';
4 | import { AuthLayout, DashboardLayout } from '../layouts';
5 | import { BearPage, Dashboard, JiraPage, LoginPage, PersonPage, WeddingInvitationPage } from '../pages';
6 |
7 |
8 | export const router = createBrowserRouter( [
9 | {
10 | path: '/',
11 | element: ,
12 | children: [
13 | /// Dashboard Routes
14 | {
15 | path: 'dashboard',
16 | element: ,
17 | children: [
18 | {
19 | path: '',
20 | element:
21 | },
22 | {
23 | path: 'bears',
24 | element:
25 | },
26 | {
27 | path: 'person',
28 | element:
29 | },
30 | {
31 | path: 'tasks',
32 | element:
33 | },
34 | {
35 | path: 'wedding-invitation',
36 | element:
37 | }
38 |
39 | ]
40 | },
41 |
42 | /// Auth Routes
43 | {
44 | path: 'auth',
45 | element: ,
46 | children: [
47 | {
48 | path: 'login',
49 | element:
50 | }
51 | ]
52 |
53 | },
54 |
55 | ],
56 | },
57 | ] );
--------------------------------------------------------------------------------
/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/dashboard/DashboardPage.tsx:
--------------------------------------------------------------------------------
1 | import { IoAccessibilityOutline, IoHeartOutline, IoListOutline, IoLockClosedOutline, IoPawOutline } from 'react-icons/io5';
2 | import { WhiteCard } from '../../components';
3 |
4 | export const Dashboard = () => {
5 | return (
6 | <>
7 | Dashboard
8 | Información colectiva de varios stores de Zustand
9 |
10 |
11 |
12 |
13 |
14 |
15 | Osos
16 | Información
17 |
18 |
19 |
20 |
21 |
22 | Persona
23 | Información
24 |
25 |
26 |
27 |
28 |
29 | Tareas
30 | Información
31 |
32 |
33 |
34 |
35 |
36 | Boda
37 | Información
38 |
39 |
40 |
41 |
42 |
43 | Auth
44 | Información
45 |
46 |
47 |
48 |
49 |
50 |
51 | >
52 | );
53 | };
--------------------------------------------------------------------------------
/src/pages/auth/LoginPage.tsx:
--------------------------------------------------------------------------------
1 | import { FormEvent } from 'react';
2 |
3 | export const LoginPage = () => {
4 |
5 | const onSubmit = (event: FormEvent ) => {
6 | event.preventDefault();
7 | // const { username, password, remember } = event.target as HTMLFormElement;
8 | const { username, password,remember } = event.target as typeof event.target & {
9 | username: { value: string };
10 | password: { value: string };
11 | remember: { checked: boolean }
12 | };
13 | console.log(username.value, password.value, remember.checked);
14 |
15 | username.value = '';
16 | password.value = '';
17 | remember.checked = false;
18 | }
19 |
20 |
21 | return (
22 | <>
23 | Login
24 |
25 |
48 |
51 | >
52 | );
53 | };
--------------------------------------------------------------------------------
/src/pages/01-basic/PersonPage.tsx:
--------------------------------------------------------------------------------
1 | import { WhiteCard } from '../../components';
2 |
3 |
4 |
5 | export const PersonPage = () => {
6 | return (
7 | <>
8 | Persona
9 | Información que se compartirá a otro store, Session Storage y Firebase
10 |
11 |
12 |
13 |
58 |
59 | >
60 | );
61 | };
--------------------------------------------------------------------------------
/src/components/jira/JiraTasks.tsx:
--------------------------------------------------------------------------------
1 | import { IoCheckmarkCircleOutline, IoEllipsisHorizontalOutline, IoReorderTwoOutline } from 'react-icons/io5';
2 |
3 | interface Props {
4 | title: string;
5 | value: 'pending' | 'in-progress' | 'done';
6 | }
7 |
8 |
9 | export const JiraTasks = ({ title }: Props) => {
10 | return (
11 |
12 |
13 |
14 | {/* Task Header */ }
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
{ title }
26 |
27 |
28 |
31 |
32 |
33 |
34 | {/* Task Items */ }
35 |
36 |
37 |
38 |
39 |
40 | Tarea número 1
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | Tarea número 2
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | );
64 | };
--------------------------------------------------------------------------------
/src/components/shared/sidemenu/SideMenu.tsx:
--------------------------------------------------------------------------------
1 | import type { IconType } from 'react-icons';
2 | import { IoSpeedometerOutline, IoPawOutline, IoLogOutOutline, IoHeartOutline, IoListOutline, IoAccessibilityOutline } from 'react-icons/io5';
3 | import { NavLink } from 'react-router-dom';
4 | import './SideMenu.css';
5 | import { SideMenuItem } from './SideMenuItem';
6 |
7 |
8 | interface MenuItem {
9 | title: string;
10 | subTitle: string;
11 | href: string;
12 | Icon: IconType;
13 | }
14 |
15 | const menuItems: MenuItem[] = [
16 | { title: 'Dashboard', subTitle: 'Visualizar data', href: '/dashboard', Icon: IoSpeedometerOutline },
17 | { title: 'Osos', subTitle: 'Manejador de osos', href: '/dashboard/bears', Icon: IoPawOutline },
18 | { title: 'Persona', subTitle: 'Nombre y apellido', href: '/dashboard/person', Icon: IoAccessibilityOutline },
19 | { title: 'Tareas', subTitle: 'Listado de tareas', href: '/dashboard/tasks', Icon: IoListOutline },
20 | { title: 'Boda', subTitle: 'Invitados a la boda', href: '/dashboard/wedding-invitation', Icon: IoHeartOutline },
21 | ];
22 |
23 |
24 |
25 |
26 | export const SideMenu = () => {
27 |
28 | return (
29 |
77 | );
78 | };
--------------------------------------------------------------------------------
/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/03-slices/WeddingInvitationPage.tsx:
--------------------------------------------------------------------------------
1 | import { WhiteCard } from '../../components';
2 |
3 |
4 |
5 | export const WeddingInvitationPage = () => {
6 | return (
7 | <>
8 | Invitación de Boda
9 | Zustand segmentado en slices
10 |
11 |
12 |
13 |
135 |
136 | >
137 | );
138 | };
--------------------------------------------------------------------------------