chartManD commited on
Commit
750887b
1 Parent(s): 60ef5c8

intalacion de nodejs

Browse files
.gitignore ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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?
Dockerfile CHANGED
@@ -1,16 +1,25 @@
1
  # Read the doc: https://huggingface.co/docs/hub/spaces-sdks-docker
2
  # you will also find guides on how best to write your Dockerfile
3
 
4
- FROM python:3.9
 
 
 
 
 
 
 
 
5
 
6
- RUN useradd -m -u 1000 user
7
- USER user
8
- ENV PATH="/home/user/.local/bin:$PATH"
9
-
10
- WORKDIR /app
11
-
12
- COPY --chown=user ./requirements.txt requirements.txt
13
- RUN pip install --no-cache-dir --upgrade -r requirements.txt
14
-
15
- COPY --chown=user . /app
16
- CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
 
 
1
  # Read the doc: https://huggingface.co/docs/hub/spaces-sdks-docker
2
  # you will also find guides on how best to write your Dockerfile
3
 
4
+ # FROM debian:12.6
5
+ # COPY ./app ./app
6
+ # COPY entrypoint.sh entrypoint.sh
7
+ # RUN chmod +x entrypoint.sh
8
+ # EXPOSE 7860
9
+ # EXPOSE 22
10
+ # RUN cat /etc/passwd
11
+ # ENTRYPOINT ["./entrypoint.sh"]
12
+ # CMD ["bash"]
13
 
14
+ FROM debian:12.6
15
+ COPY . ./app
16
+ COPY entrypoint.sh entrypoint.sh
17
+ RUN chmod +x entrypoint.sh
18
+ EXPOSE 7860
19
+ EXPOSE 80
20
+ EXPOSE 22
21
+ WORKDIR ./app
22
+ #USER root
23
+ RUN cat /etc/passwd
24
+ ENTRYPOINT ["./entrypoint.sh"]
25
+ CMD ["bash"]
README.md CHANGED
@@ -7,5 +7,4 @@ sdk: docker
7
  pinned: false
8
  app_port: 7860
9
  ---
10
-
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
7
  pinned: false
8
  app_port: 7860
9
  ---
10
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
app.py DELETED
@@ -1,7 +0,0 @@
1
- from fastapi import FastAPI
2
-
3
- app = FastAPI()
4
-
5
- @app.get("/")
6
- def greet_json():
7
- return {"Hello": "World!"}
 
 
 
 
 
 
 
 
entrypoint.sh ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #! /bin/sh
2
+ apt update -y
3
+ apt upgrade -y
4
+ apt -y install apache2
5
+ apt -y install nodejs
6
+
7
+ node -v
8
+
9
+ rm -rf /var/lib/apt/lists/*
10
+
11
+ # pwd
12
+ # ls -la
13
+ # whoamiz
14
+ # id
15
+ ls
16
+ node mocos.js
eslint.config.js ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import js from '@eslint/js'
2
+ import globals from 'globals'
3
+ import react from 'eslint-plugin-react'
4
+ import reactHooks from 'eslint-plugin-react-hooks'
5
+ import reactRefresh from 'eslint-plugin-react-refresh'
6
+
7
+ export default [
8
+ { ignores: ['dist'] },
9
+ {
10
+ files: ['**/*.{js,jsx}'],
11
+ languageOptions: {
12
+ ecmaVersion: 2020,
13
+ globals: globals.browser,
14
+ parserOptions: {
15
+ ecmaVersion: 'latest',
16
+ ecmaFeatures: { jsx: true },
17
+ sourceType: 'module',
18
+ },
19
+ },
20
+ settings: { react: { version: '18.3' } },
21
+ plugins: {
22
+ react,
23
+ 'react-hooks': reactHooks,
24
+ 'react-refresh': reactRefresh,
25
+ },
26
+ rules: {
27
+ ...js.configs.recommended.rules,
28
+ ...react.configs.recommended.rules,
29
+ ...react.configs['jsx-runtime'].rules,
30
+ ...reactHooks.configs.recommended.rules,
31
+ 'react/jsx-no-target-blank': 'off',
32
+ 'react-refresh/only-export-components': [
33
+ 'warn',
34
+ { allowConstantExport: true },
35
+ ],
36
+ },
37
+ },
38
+ ]
index.html ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
+ <title>Tutorial - react-router-dom</title>
9
+ </head>
10
+
11
+ <body>
12
+ <div id="root"></div>
13
+ <script type="module" src="/src/main.jsx"></script>
14
+ </body>
15
+
16
+ </html>
mocos.js ADDED
@@ -0,0 +1 @@
 
 
1
+ console.log("Ahhhhhhhhh yo soy node en el SO de tu sistema");
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "tutorial-react-router",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "lint": "eslint .",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "localforage": "^1.10.0",
14
+ "match-sorter": "^6.3.4",
15
+ "react": "^18.3.1",
16
+ "react-dom": "^18.3.1",
17
+ "react-router-dom": "^6.26.1",
18
+ "sort-by": "^1.2.0"
19
+ },
20
+ "devDependencies": {
21
+ "@eslint/js": "^9.9.0",
22
+ "@types/react": "^18.3.3",
23
+ "@types/react-dom": "^18.3.0",
24
+ "@vitejs/plugin-react": "^4.3.1",
25
+ "eslint": "^9.9.0",
26
+ "eslint-plugin-react": "^7.35.0",
27
+ "eslint-plugin-react-hooks": "^5.1.0-rc.0",
28
+ "eslint-plugin-react-refresh": "^0.4.9",
29
+ "globals": "^15.9.0",
30
+ "vite": "^5.4.1"
31
+ }
32
+ }
public/vite.svg ADDED
requirements.txt DELETED
@@ -1,2 +0,0 @@
1
- fastapi
2
- uvicorn[standard]
 
 
 
src/contacts.js ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import localforage from "localforage";
2
+ import { matchSorter } from "match-sorter";
3
+ import sortBy from "sort-by";
4
+
5
+ export async function getContacts(query) {
6
+ await fakeNetwork(`getContacts:${query}`);
7
+ let contacts = await localforage.getItem("contacts");
8
+ if (!contacts) contacts = [];
9
+ if (query) {
10
+ contacts = matchSorter(contacts, query, { keys: ["first", "last"] });
11
+ }
12
+ return contacts.sort(sortBy("last", "createdAt"));
13
+ }
14
+
15
+ export async function createContact() {
16
+ await fakeNetwork();
17
+ let id = Math.random().toString(36).substring(2, 9);
18
+ let contact = { id, createdAt: Date.now() };
19
+ let contacts = await getContacts();
20
+ contacts.unshift(contact);
21
+ await set(contacts);
22
+ return contact;
23
+ }
24
+
25
+ export async function getContact(id) {
26
+ await fakeNetwork(`contact:${id}`);
27
+ let contacts = await localforage.getItem("contacts");
28
+ let contact = contacts.find((contact) => contact.id === id);
29
+ return contact ?? null;
30
+ }
31
+
32
+ export async function updateContact(id, updates) {
33
+ await fakeNetwork();
34
+ let contacts = await localforage.getItem("contacts");
35
+ let contact = contacts.find((contact) => contact.id === id);
36
+ if (!contact) throw new Error("No contact found for", id);
37
+ Object.assign(contact, updates);
38
+ await set(contacts);
39
+ return contact;
40
+ }
41
+
42
+ export async function deleteContact(id) {
43
+ let contacts = await localforage.getItem("contacts");
44
+ let index = contacts.findIndex((contact) => contact.id === id);
45
+ if (index > -1) {
46
+ contacts.splice(index, 1);
47
+ await set(contacts);
48
+ return true;
49
+ }
50
+ return false;
51
+ }
52
+
53
+ function set(contacts) {
54
+ return localforage.setItem("contacts", contacts);
55
+ }
56
+
57
+ // fake a cache so we don't slow down stuff we've already seen
58
+ let fakeCache = {};
59
+
60
+ async function fakeNetwork(key) {
61
+ if (!key) {
62
+ fakeCache = {};
63
+ }
64
+
65
+ if (fakeCache[key]) {
66
+ return;
67
+ }
68
+
69
+ fakeCache[key] = true;
70
+ return new Promise((res) => {
71
+ setTimeout(res, Math.random() * 800);
72
+ });
73
+ }
src/error-page.jsx ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useRouteError } from "react-router-dom";
2
+
3
+ export default function ErrorPage() {
4
+ const error = useRouteError();
5
+ console.error(error);
6
+
7
+ return (
8
+ <div id="error-page">
9
+ <h1>Oops!</h1>
10
+ <p>Sorry, an unexpected error has occurred.</p>
11
+ <p>
12
+ <i>{error.statusText || error.message}</i>
13
+ </p>
14
+ </div>
15
+ );
16
+ }
src/index.css ADDED
@@ -0,0 +1,398 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ html {
2
+ box-sizing: border-box;
3
+ }
4
+
5
+ *,
6
+ *:before,
7
+ *:after {
8
+ box-sizing: inherit;
9
+ }
10
+
11
+ body {
12
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
13
+ "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
14
+ sans-serif;
15
+ -webkit-font-smoothing: antialiased;
16
+ -moz-osx-font-smoothing: grayscale;
17
+ }
18
+
19
+ code {
20
+ font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
21
+ monospace;
22
+ }
23
+
24
+ html,
25
+ body {
26
+ height: 100%;
27
+ margin: 0;
28
+ line-height: 1.5;
29
+ color: #121212;
30
+ }
31
+
32
+ textarea,
33
+ input,
34
+ button {
35
+ font-size: 1rem;
36
+ font-family: inherit;
37
+ border: none;
38
+ border-radius: 8px;
39
+ padding: 0.5rem 0.75rem;
40
+ box-shadow: 0 0px 1px hsla(0, 0%, 0%, 0.2), 0 1px 2px hsla(0, 0%, 0%, 0.2);
41
+ background-color: white;
42
+ line-height: 1.5;
43
+ margin: 0;
44
+ }
45
+
46
+ button {
47
+ color: #3992ff;
48
+ font-weight: 500;
49
+ }
50
+
51
+ textarea:hover,
52
+ input:hover,
53
+ button:hover {
54
+ box-shadow: 0 0px 1px hsla(0, 0%, 0%, 0.6), 0 1px 2px hsla(0, 0%, 0%, 0.2);
55
+ }
56
+
57
+ button:active {
58
+ box-shadow: 0 0px 1px hsla(0, 0%, 0%, 0.4);
59
+ transform: translateY(1px);
60
+ }
61
+
62
+ #contact h1 {
63
+ display: flex;
64
+ align-items: flex-start;
65
+ gap: 1rem;
66
+ }
67
+
68
+ #contact h1 form {
69
+ display: flex;
70
+ align-items: center;
71
+ margin-top: 0.25rem;
72
+ }
73
+
74
+ #contact h1 form button {
75
+ box-shadow: none;
76
+ font-size: 1.5rem;
77
+ font-weight: 400;
78
+ padding: 0;
79
+ }
80
+
81
+ #contact h1 form button[value="true"] {
82
+ color: #a4a4a4;
83
+ }
84
+
85
+ #contact h1 form button[value="true"]:hover,
86
+ #contact h1 form button[value="false"] {
87
+ color: #eeb004;
88
+ }
89
+
90
+ form[action$="destroy"] button {
91
+ color: #f44250;
92
+ }
93
+
94
+ .sr-only {
95
+ position: absolute;
96
+ width: 1px;
97
+ height: 1px;
98
+ padding: 0;
99
+ margin: -1px;
100
+ overflow: hidden;
101
+ clip: rect(0, 0, 0, 0);
102
+ white-space: nowrap;
103
+ border-width: 0;
104
+ }
105
+
106
+ #root {
107
+ display: flex;
108
+ height: 100%;
109
+ width: 100%;
110
+ }
111
+
112
+ #sidebar {
113
+ width: 22rem;
114
+ background-color: #f7f7f7;
115
+ border-right: solid 1px #e3e3e3;
116
+ display: flex;
117
+ flex-direction: column;
118
+ }
119
+
120
+ #sidebar>* {
121
+ padding-left: 2rem;
122
+ padding-right: 2rem;
123
+ }
124
+
125
+ #sidebar h1 {
126
+ font-size: 1rem;
127
+ font-weight: 500;
128
+ display: flex;
129
+ align-items: center;
130
+ margin: 0;
131
+ padding: 1rem 2rem;
132
+ border-top: 1px solid #e3e3e3;
133
+ order: 1;
134
+ line-height: 1;
135
+ }
136
+
137
+ #sidebar h1::before {
138
+ content: url("data:image/svg+xml,%3Csvg width='25' height='18' viewBox='0 0 25 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M19.4127 6.4904C18.6984 6.26581 18.3295 6.34153 17.5802 6.25965C16.4219 6.13331 15.9604 5.68062 15.7646 4.51554C15.6551 3.86516 15.7844 2.9129 15.5048 2.32334C14.9699 1.19921 13.7183 0.695046 12.461 0.982805C11.3994 1.22611 10.516 2.28708 10.4671 3.37612C10.4112 4.61957 11.1197 5.68054 12.3363 6.04667C12.9143 6.22097 13.5284 6.3087 14.132 6.35315C15.2391 6.43386 15.3241 7.04923 15.6236 7.55574C15.8124 7.87508 15.9954 8.18975 15.9954 9.14193C15.9954 10.0941 15.8112 10.4088 15.6236 10.7281C15.3241 11.2334 14.9547 11.5645 13.8477 11.6464C13.244 11.6908 12.6288 11.7786 12.0519 11.9528C10.8353 12.3201 10.1268 13.3799 10.1828 14.6234C10.2317 15.7124 11.115 16.7734 12.1766 17.0167C13.434 17.3056 14.6855 16.8003 15.2204 15.6762C15.5013 15.0866 15.6551 14.4187 15.7646 13.7683C15.9616 12.6032 16.423 12.1505 17.5802 12.0242C18.3295 11.9423 19.1049 12.0242 19.8071 11.6253C20.5491 11.0832 21.212 10.2696 21.212 9.14192C21.212 8.01428 20.4976 6.83197 19.4127 6.4904Z' fill='%23F44250'/%3E%3Cpath d='M7.59953 11.7459C6.12615 11.7459 4.92432 10.5547 4.92432 9.09441C4.92432 7.63407 6.12615 6.44287 7.59953 6.44287C9.0729 6.44287 10.2747 7.63407 10.2747 9.09441C10.2747 10.5536 9.07172 11.7459 7.59953 11.7459Z' fill='black'/%3E%3Cpath d='M2.64217 17.0965C1.18419 17.093 -0.0034949 15.8971 7.72743e-06 14.4356C0.00352588 12.9765 1.1994 11.7888 2.66089 11.7935C4.12004 11.797 5.30772 12.9929 5.30306 14.4544C5.29953 15.9123 4.10366 17.1 2.64217 17.0965Z' fill='black'/%3E%3Cpath d='M22.3677 17.0965C20.9051 17.1046 19.7046 15.9217 19.6963 14.4649C19.6882 13.0023 20.8712 11.8017 22.3279 11.7935C23.7906 11.7854 24.9911 12.9683 24.9993 14.4251C25.0075 15.8866 23.8245 17.0883 22.3677 17.0965Z' fill='black'/%3E%3C/svg%3E%0A");
139
+ margin-right: 0.5rem;
140
+ position: relative;
141
+ top: 1px;
142
+ }
143
+
144
+ #sidebar>div {
145
+ display: flex;
146
+ align-items: center;
147
+ gap: 0.5rem;
148
+ padding-top: 1rem;
149
+ padding-bottom: 1rem;
150
+ border-bottom: 1px solid #e3e3e3;
151
+ }
152
+
153
+ #sidebar>div form {
154
+ position: relative;
155
+ }
156
+
157
+ #sidebar>div form input[type="search"] {
158
+ width: 100%;
159
+ padding-left: 2rem;
160
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-6 w-6' fill='none' viewBox='0 0 24 24' stroke='%23999' stroke-width='2'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z' /%3E%3C/svg%3E");
161
+ background-repeat: no-repeat;
162
+ background-position: 0.625rem 0.75rem;
163
+ background-size: 1rem;
164
+ position: relative;
165
+ }
166
+
167
+ #sidebar>div form input[type="search"].loading {
168
+ background-image: none;
169
+ }
170
+
171
+ #search-spinner {
172
+ width: 1rem;
173
+ height: 1rem;
174
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24'%3E%3Cpath stroke='%23000' strokeLinecap='round' strokeLinejoin='round' strokeWidth='2' d='M20 4v5h-.582m0 0a8.001 8.001 0 00-15.356 2m15.356-2H15M4 20v-5h.581m0 0a8.003 8.003 0 0015.357-2M4.581 15H9' /%3E%3C/svg%3E");
175
+ animation: spin 1s infinite linear;
176
+ position: absolute;
177
+ left: 0.625rem;
178
+ top: 0.75rem;
179
+ }
180
+
181
+ @keyframes spin {
182
+ from {
183
+ transform: rotate(0deg);
184
+ }
185
+
186
+ to {
187
+ transform: rotate(360deg);
188
+ }
189
+ }
190
+
191
+ #sidebar nav {
192
+ flex: 1;
193
+ overflow: auto;
194
+ padding-top: 1rem;
195
+ }
196
+
197
+ #sidebar nav a span {
198
+ float: right;
199
+ color: #eeb004;
200
+ }
201
+
202
+ #sidebar nav a.active span {
203
+ color: inherit;
204
+ }
205
+
206
+ i {
207
+ color: #818181;
208
+ }
209
+
210
+ #sidebar nav .active i {
211
+ color: inherit;
212
+ }
213
+
214
+ #sidebar ul {
215
+ padding: 0;
216
+ margin: 0;
217
+ list-style: none;
218
+ }
219
+
220
+ #sidebar li {
221
+ margin: 0.25rem 0;
222
+ }
223
+
224
+ #sidebar nav a {
225
+ display: flex;
226
+ align-items: center;
227
+ justify-content: space-between;
228
+ overflow: hidden;
229
+
230
+ white-space: pre;
231
+ padding: 0.5rem;
232
+ border-radius: 8px;
233
+ color: inherit;
234
+ text-decoration: none;
235
+ gap: 1rem;
236
+ }
237
+
238
+ #sidebar nav a:hover {
239
+ background: #e3e3e3;
240
+ }
241
+
242
+ #sidebar nav a.active {
243
+ background: hsl(224, 98%, 58%);
244
+ color: white;
245
+ }
246
+
247
+ #sidebar nav a.pending {
248
+ color: hsl(224, 98%, 58%);
249
+ }
250
+
251
+ #detail {
252
+ flex: 1;
253
+ padding: 2rem 4rem;
254
+ width: 100%;
255
+ }
256
+
257
+ #detail.loading {
258
+ opacity: 0.25;
259
+ transition: opacity 200ms;
260
+ transition-delay: 200ms;
261
+ }
262
+
263
+ #contact {
264
+ max-width: 40rem;
265
+ display: flex;
266
+ }
267
+
268
+ #contact h1 {
269
+ font-size: 2rem;
270
+ font-weight: 700;
271
+ margin: 0;
272
+ line-height: 1.2;
273
+ }
274
+
275
+ #contact h1+p {
276
+ margin: 0;
277
+ }
278
+
279
+ #contact h1+p+p {
280
+ white-space: break-spaces;
281
+ }
282
+
283
+ #contact h1:focus {
284
+ outline: none;
285
+ color: hsl(224, 98%, 58%);
286
+ }
287
+
288
+ #contact a[href*="twitter"] {
289
+ display: flex;
290
+ font-size: 1.5rem;
291
+ color: #3992ff;
292
+ text-decoration: none;
293
+ }
294
+
295
+ #contact a[href*="twitter"]:hover {
296
+ text-decoration: underline;
297
+ }
298
+
299
+ #contact img {
300
+ width: 12rem;
301
+ height: 12rem;
302
+ background: #c8c8c8;
303
+ margin-right: 2rem;
304
+ border-radius: 1.5rem;
305
+ object-fit: cover;
306
+ }
307
+
308
+ #contact h1~div {
309
+ display: flex;
310
+ gap: 0.5rem;
311
+ margin: 1rem 0;
312
+ }
313
+
314
+ #contact-form {
315
+ display: flex;
316
+ max-width: 40rem;
317
+ flex-direction: column;
318
+ gap: 1rem;
319
+ }
320
+
321
+ #contact-form>p:first-child {
322
+ margin: 0;
323
+ padding: 0;
324
+ }
325
+
326
+ #contact-form>p:first-child> :nth-child(2) {
327
+ margin-right: 1rem;
328
+ }
329
+
330
+ #contact-form>p:first-child,
331
+ #contact-form label {
332
+ display: flex;
333
+ }
334
+
335
+ #contact-form p:first-child span,
336
+ #contact-form label span {
337
+ width: 8rem;
338
+ }
339
+
340
+ #contact-form p:first-child input,
341
+ #contact-form label input,
342
+ #contact-form label textarea {
343
+ flex-grow: 2;
344
+ }
345
+
346
+ #contact-form-avatar {
347
+ margin-right: 2rem;
348
+ }
349
+
350
+ #contact-form-avatar img {
351
+ width: 12rem;
352
+ height: 12rem;
353
+ background: hsla(0, 0%, 0%, 0.2);
354
+ border-radius: 1rem;
355
+ }
356
+
357
+ #contact-form-avatar input {
358
+ box-sizing: border-box;
359
+ width: 100%;
360
+ }
361
+
362
+ #contact-form p:last-child {
363
+ display: flex;
364
+ gap: 0.5rem;
365
+ margin: 0 0 0 8rem;
366
+ }
367
+
368
+ #contact-form p:last-child button[type="button"] {
369
+ color: inherit;
370
+ }
371
+
372
+ #zero-state {
373
+ margin: 2rem auto;
374
+ text-align: center;
375
+ color: #818181;
376
+ }
377
+
378
+ #zero-state a {
379
+ color: inherit;
380
+ }
381
+
382
+ #zero-state a:hover {
383
+ color: #121212;
384
+ }
385
+
386
+ #zero-state:before {
387
+ display: block;
388
+ margin-bottom: 0.5rem;
389
+ content: url("data:image/svg+xml,%3Csvg width='50' height='33' viewBox='0 0 50 33' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M38.8262 11.1744C37.3975 10.7252 36.6597 10.8766 35.1611 10.7128C32.8444 10.4602 31.9215 9.55475 31.5299 7.22456C31.3108 5.92377 31.5695 4.01923 31.0102 2.8401C29.9404 0.591789 27.4373 -0.416556 24.9225 0.158973C22.7992 0.645599 21.0326 2.76757 20.9347 4.94569C20.8228 7.43263 22.2399 9.5546 24.6731 10.2869C25.8291 10.6355 27.0574 10.8109 28.2646 10.8998C30.4788 11.0613 30.6489 12.292 31.2479 13.3051C31.6255 13.9438 31.9914 14.5731 31.9914 16.4775C31.9914 18.3819 31.6231 19.0112 31.2479 19.6499C30.6489 20.6606 29.9101 21.3227 27.696 21.4865C26.4887 21.5754 25.2581 21.7508 24.1044 22.0994C21.6712 22.834 20.2542 24.9537 20.366 27.4406C20.4639 29.6187 22.2306 31.7407 24.3538 32.2273C26.8686 32.8052 29.3717 31.7945 30.4415 29.5462C31.0032 28.3671 31.3108 27.0312 31.5299 25.7304C31.9238 23.4002 32.8467 22.4948 35.1611 22.2421C36.6597 22.0784 38.2107 22.2421 39.615 21.4443C41.099 20.36 42.4248 18.7328 42.4248 16.4775C42.4248 14.2222 40.9961 11.8575 38.8262 11.1744Z' fill='%23E3E3E3'/%3E%3Cpath d='M15.1991 21.6854C12.2523 21.6854 9.84863 19.303 9.84863 16.3823C9.84863 13.4615 12.2523 11.0791 15.1991 11.0791C18.1459 11.0791 20.5497 13.4615 20.5497 16.3823C20.5497 19.3006 18.1436 21.6854 15.1991 21.6854Z' fill='%23E3E3E3'/%3E%3Cpath d='M5.28442 32.3871C2.36841 32.38 -0.00698992 29.9882 1.54551e-05 27.0652C0.00705187 24.1469 2.39884 21.7715 5.32187 21.7808C8.24022 21.7878 10.6156 24.1796 10.6063 27.1027C10.5992 30.0187 8.20746 32.3941 5.28442 32.3871Z' fill='%23E3E3E3'/%3E%3Cpath d='M44.736 32.387C41.8107 32.4033 39.4096 30.0373 39.3932 27.1237C39.3769 24.1984 41.7428 21.7973 44.6564 21.7808C47.5817 21.7645 49.9828 24.1305 49.9993 27.0441C50.0156 29.9671 47.6496 32.3705 44.736 32.387Z' fill='%23E3E3E3'/%3E%3C/svg%3E%0A");
390
+ }
391
+
392
+ #error-page {
393
+ display: flex;
394
+ flex-direction: column;
395
+ align-items: center;
396
+ justify-content: center;
397
+ width: 100%;
398
+ }
src/main.jsx ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import * as ReactDOM from "react-dom/client";
3
+ import { createBrowserRouter, RouterProvider } from "react-router-dom";
4
+ import "./index.css";
5
+ import Root, {
6
+ loader as rootLoader,
7
+ action as rootAction,
8
+ } from "./routes/root";
9
+ import ErrorPage from "./error-page";
10
+ import Contact, {
11
+ loader as contactLoader,
12
+ action as contactAction,
13
+ } from "./routes/contact";
14
+ import EditContact, { action as editAction } from "./routes/edit";
15
+ import { action as destroyAction } from "./routes/destroy";
16
+ import Index from "./routes";
17
+ import { AuthProvider } from "./providers/AuthProvider";
18
+
19
+ const router = createBrowserRouter([
20
+ {
21
+ path: "/",
22
+ element: <Root />,
23
+ loader: rootLoader,
24
+ action: rootAction,
25
+ errorElement: <ErrorPage />,
26
+ children: [
27
+ {
28
+ errorElement: <ErrorPage />,
29
+ children: [
30
+ { index: true, element: <Index /> },
31
+ {
32
+ path: "contacts/:contactId",
33
+ element: <Contact />,
34
+ loader: contactLoader,
35
+ action: contactAction,
36
+ },
37
+ {
38
+ path: "contacts/:contactId/edit",
39
+ element: <EditContact />,
40
+ loader: contactLoader,
41
+ action: editAction,
42
+ },
43
+ {
44
+ path: "contacts/:contactId/destroy",
45
+ action: destroyAction,
46
+ errorElement: <div>Oops! There was an error.</div>,
47
+ },
48
+ ],
49
+ },
50
+ ],
51
+ },
52
+ {
53
+ path: "contacts/:contactId",
54
+ element: <Contact />,
55
+ },
56
+ ]);
57
+
58
+ ReactDOM.createRoot(document.getElementById("root")).render(
59
+ <React.StrictMode>
60
+ <AuthProvider>
61
+ <RouterProvider router={router} />
62
+ </AuthProvider>
63
+ </React.StrictMode>
64
+ );
src/providers/AuthProvider.jsx ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { createContext, useContext, useEffect, useState } from "react";
2
+
3
+ const AuthContext = createContext();
4
+
5
+ export function AuthProvider({ children }) {
6
+ const [isAuthenticated, setIsAuthenticated] = useState(false); // para saber si el usario esta autentificado
7
+ const [user, setUser] = useState({ name: "mocos" }); // la informacion util del usario como nombre, usarname, correo, etc.
8
+ const [isRegisterGlucosa, setIsRegisterGlucosa] = useState(false); // saber si el usario ya ha registrado su glucosa el dia actual
9
+ const [update, setUpdate] = useState(false);
10
+
11
+ const changeAuthenticat = () => {
12
+ setIsAuthenticated(!isAuthenticated);
13
+ };
14
+
15
+ const changeUser = (newUser) => {
16
+ setUser(newUser);
17
+ };
18
+
19
+ const confirmRegisterGlucosa = () => {
20
+ setIsRegisterGlucosa(!isRegisterGlucosa);
21
+ };
22
+
23
+ useEffect(() => {
24
+ setUpdate(false);
25
+ }, [update, user]);
26
+
27
+ return (
28
+ <AuthContext.Provider
29
+ value={{
30
+ isAuthenticated,
31
+ user,
32
+ isRegisterGlucosa,
33
+ changeAuthenticat,
34
+ changeUser,
35
+ confirmRegisterGlucosa,
36
+ }}
37
+ >
38
+ {children}
39
+ </AuthContext.Provider>
40
+ );
41
+ }
42
+
43
+ export const useAuth = () => useContext(AuthContext);
src/routes/contact.jsx ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Form, useFetcher, useLoaderData } from "react-router-dom";
2
+ import { getContact, updateContact } from "../contacts";
3
+
4
+ export async function action({ request, params }) {
5
+ const formData = await request.formData();
6
+ return updateContact(params.contactId, {
7
+ favorite: formData.get("favorite") === "true",
8
+ });
9
+ }
10
+
11
+ export async function loader({ params }) {
12
+ const contact = await getContact(params.contactId);
13
+ if (!contact) {
14
+ throw new Response("", {
15
+ status: 404,
16
+ statusText: "Not Found",
17
+ });
18
+ }
19
+ return { contact };
20
+ }
21
+
22
+ export default function Contact() {
23
+ const { contact } = useLoaderData();
24
+
25
+ return (
26
+ <div id="contact">
27
+ <div>
28
+ <img
29
+ key={contact.avatar}
30
+ src={
31
+ contact.avatar ||
32
+ `https://robohash.org/${contact.id}.png?size=200x200`
33
+ }
34
+ />
35
+ </div>
36
+
37
+ <div>
38
+ <h1>
39
+ {contact.first || contact.last ? (
40
+ <>
41
+ {contact.first} {contact.last}
42
+ </>
43
+ ) : (
44
+ <i>No Name</i>
45
+ )}{" "}
46
+ <Favorite contact={contact} />
47
+ </h1>
48
+
49
+ {contact.twitter && (
50
+ <p>
51
+ <a target="_blank" href={`https://twitter.com/${contact.twitter}`}>
52
+ {contact.twitter}
53
+ </a>
54
+ </p>
55
+ )}
56
+
57
+ {contact.notes && <p>{contact.notes}</p>}
58
+
59
+ <div>
60
+ <Form action="edit">
61
+ <button type="submit">Edit</button>
62
+ </Form>
63
+ <Form
64
+ method="post"
65
+ action="destroy"
66
+ onSubmit={(event) => {
67
+ if (!confirm("Please confirm you want to delete this record.")) {
68
+ event.preventDefault();
69
+ }
70
+ }}
71
+ >
72
+ <button type="submit">Delete</button>
73
+ </Form>
74
+ </div>
75
+ </div>
76
+ </div>
77
+ );
78
+ }
79
+
80
+ function Favorite({ contact }) {
81
+ const fetcher = useFetcher();
82
+
83
+ const favorite = fetcher.formData
84
+ ? fetcher.formData.get("favorite") === "true"
85
+ : contact.favorite;
86
+
87
+ return (
88
+ <fetcher.Form method="post">
89
+ <button
90
+ name="favorite"
91
+ value={favorite ? "false" : "true"}
92
+ aria-label={favorite ? "Remove from favorites" : "Add to favorites"}
93
+ >
94
+ {favorite ? "★" : "☆"}
95
+ </button>
96
+ </fetcher.Form>
97
+ );
98
+ }
src/routes/destroy.jsx ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ import { redirect } from "react-router-dom";
2
+ import { deleteContact } from "../contacts";
3
+
4
+ export async function action({ params }) {
5
+ await deleteContact(params.contactId);
6
+ return redirect("/");
7
+ }
src/routes/edit.jsx ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Form, redirect, useLoaderData, useNavigate } from "react-router-dom";
2
+ import { updateContact } from "../contacts";
3
+
4
+ export async function action({ request, params }) {
5
+ const formData = await request.formData();
6
+ const updates = Object.fromEntries(formData);
7
+ await updateContact(params.contactId, updates);
8
+ return redirect(`/contacts/${params.contactId}`);
9
+ }
10
+
11
+ export default function EditContact() {
12
+ const { contact } = useLoaderData();
13
+ const navigate = useNavigate();
14
+
15
+ return (
16
+ <Form method="post" id="contact-form">
17
+ <p>
18
+ <span>Name</span>
19
+ <input
20
+ placeholder="First"
21
+ aria-label="First name"
22
+ type="text"
23
+ name="first"
24
+ defaultValue={contact?.first}
25
+ />
26
+ <input
27
+ placeholder="Last"
28
+ aria-label="Last name"
29
+ type="text"
30
+ name="last"
31
+ defaultValue={contact?.last}
32
+ />
33
+ </p>
34
+ <label>
35
+ <span>Twitter</span>
36
+ <input
37
+ type="text"
38
+ name="twitter"
39
+ placeholder="@jack"
40
+ defaultValue={contact?.twitter}
41
+ />
42
+ </label>
43
+ <label>
44
+ <span>Avatar URL</span>
45
+ <input
46
+ placeholder="https://example.com/avatar.jpg"
47
+ aria-label="Avatar URL"
48
+ type="text"
49
+ name="avatar"
50
+ defaultValue={contact?.avatar}
51
+ />
52
+ </label>
53
+ <label>
54
+ <span>Notes</span>
55
+ <textarea name="notes" defaultValue={contact?.notes} rows={6} />
56
+ </label>
57
+ <p>
58
+ <button type="submit">Save</button>
59
+ <button
60
+ type="button"
61
+ onClick={() => {
62
+ navigate(-1);
63
+ }}
64
+ >
65
+ Cancel
66
+ </button>
67
+ </p>
68
+ </Form>
69
+ );
70
+ }
src/routes/index.jsx ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ export default function Index() {
2
+ return (
3
+ <p id="zero-state">
4
+ This is a demo for React Router.
5
+ <br />
6
+ Check out{" "}
7
+ <a href="https://reactrouter.com">the docs at reactrouter.com</a>.
8
+ </p>
9
+ );
10
+ }
src/routes/root.jsx ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ Form,
3
+ Outlet,
4
+ useLoaderData,
5
+ redirect,
6
+ NavLink,
7
+ useNavigation,
8
+ useSubmit,
9
+ } from "react-router-dom";
10
+ import { createContact, getContacts } from "../contacts";
11
+ import { useEffect } from "react";
12
+ import { useAuth } from "../providers/AuthProvider";
13
+
14
+ export async function loader({ request }) {
15
+ const url = new URL(request.url);
16
+ const q = url.searchParams.get("q");
17
+ const contacts = await getContacts(q);
18
+ return { contacts, q };
19
+ }
20
+
21
+ export async function action() {
22
+ const contact = await createContact();
23
+ return redirect(`/contacts/${contact.id}/edit`);
24
+ }
25
+
26
+ export default function Root() {
27
+ const { contacts, q } = useLoaderData();
28
+ const navigation = useNavigation();
29
+ const submit = useSubmit();
30
+
31
+ const { user } = useAuth();
32
+
33
+ const searching =
34
+ navigation.location &&
35
+ new URLSearchParams(navigation.location.search).has("q");
36
+
37
+ useEffect(() => {
38
+ document.getElementById("q").value = q;
39
+ }, [q]);
40
+
41
+ return (
42
+ <>
43
+ <div id="sidebar">
44
+ <h1>React Router Contacts</h1>
45
+ <div>
46
+ <Form id="search-form" role="search">
47
+ <input
48
+ id="q"
49
+ className={searching ? "loading" : ""}
50
+ aria-label="Search contacts"
51
+ placeholder="Search"
52
+ type="search"
53
+ name="q"
54
+ defaultValue={q}
55
+ onChange={(event) => {
56
+ const isFirstSearch = q == null;
57
+ submit(event.currentTarget.form, {
58
+ replace: !isFirstSearch,
59
+ });
60
+ }}
61
+ />
62
+ <div id="search-spinner" aria-hidden hidden={!searching} />
63
+ <div className="sr-only" aria-live="polite"></div>
64
+ </Form>
65
+ <Form method="post">
66
+ <button type="submit">New</button>
67
+ </Form>
68
+ </div>
69
+ <nav>
70
+ <ul>
71
+ {contacts.length ? (
72
+ <ul>
73
+ {contacts.map((contact) => (
74
+ <li key={contact.id}>
75
+ <NavLink
76
+ to={`contacts/${contact.id}`}
77
+ className={({ isActive, isPending }) =>
78
+ isActive ? "active" : isPending ? "pending" : ""
79
+ }
80
+ >
81
+ {contact.first || contact.last ? (
82
+ <>
83
+ {contact.first} {contact.last}
84
+ </>
85
+ ) : (
86
+ <i>No Name</i>
87
+ )}{" "}
88
+ {contact.favorite && <span>★</span>}
89
+ </NavLink>
90
+ </li>
91
+ ))}
92
+ </ul>
93
+ ) : (
94
+ <p>
95
+ <i>No contacts</i>
96
+ </p>
97
+ )}
98
+ </ul>
99
+ </nav>
100
+ </div>
101
+ <div
102
+ id="detail"
103
+ className={navigation.state === "loading" ? "loading" : ""}
104
+ >
105
+ <p>Un mensaje {user.name}</p>
106
+ <Outlet />
107
+ </div>
108
+ </>
109
+ );
110
+ }
vite.config.js ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
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
+ })