Loading...
18 |
19 | export const Empty = () => ) => {
24 | return
25 | }
26 |
--------------------------------------------------------------------------------
/web/src/components/User/UserForm/UserForm.tsx:
--------------------------------------------------------------------------------
1 | import { Form, FormError, FieldError, Label, TextField, Submit } from "@redwoodjs/forms"
2 |
3 | const UserForm = (props) => {
4 | const onSubmit = (data) => {
5 | props.onSave(data, props?.user?.id)
6 | }
7 |
8 | return (
9 |
43 | )
44 | }
45 |
46 | export default UserForm
47 |
--------------------------------------------------------------------------------
/web/src/components/User/Users/Users.tsx:
--------------------------------------------------------------------------------
1 | import { Link, routes } from "@redwoodjs/router"
2 | import { DeleteButton } from "src/components/DeleteButton"
3 |
4 | const MAX_STRING_LENGTH = 150
5 |
6 | const truncate = (text) => {
7 | let output = text
8 | if (text && text.length > MAX_STRING_LENGTH) {
9 | output = output.substring(0, MAX_STRING_LENGTH) + "..."
10 | }
11 | return output
12 | }
13 |
14 | const UsersList = ({ users }) => {
15 | return (
16 |
17 |
18 |
19 |
20 | id
21 | slug
22 | Email
23 | Name
24 |
25 |
26 |
27 |
28 | {users.map((user) => (
29 |
30 | {truncate(user.id)}
31 | {truncate(user.slug)}
32 | {truncate(user.email)}
33 | {truncate(user.name)}
34 |
35 |
36 |
41 | Show
42 |
43 |
48 | Edit
49 |
50 |
51 |
52 |
53 |
54 | ))}
55 |
56 |
57 |
58 | )
59 | }
60 |
61 | export default UsersList
62 |
--------------------------------------------------------------------------------
/web/src/components/User/UsersCell/UsersCell.tsx:
--------------------------------------------------------------------------------
1 | import type { FindUsers } from "types/graphql"
2 | import type { CellSuccessProps, CellFailureProps } from "@redwoodjs/web"
3 |
4 | import { Link, routes } from "@redwoodjs/router"
5 |
6 | import Users from "src/components/User/Users"
7 |
8 | export const QUERY = gql`
9 | query FindUsers {
10 | users {
11 | id
12 | slug
13 | email
14 | name
15 | }
16 | }
17 | `
18 |
19 | export const Loading = () => Loading...
20 |
21 | export const Empty = () => {
22 | return (
23 |
24 | {"No users yet. "}
25 |
26 | {"Create one?"}
27 |
28 |
29 | )
30 | }
31 |
32 | export const Failure = ({ error }: CellFailureProps) => {error.message}
33 |
34 | export const Success = ({ users }: CellSuccessProps) => {
35 | return
36 | }
37 |
--------------------------------------------------------------------------------
/web/src/index.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/orta/redwood-object-identification/335ce11b67bc302d75145dc1e2db37bdaeee4736/web/src/index.css
--------------------------------------------------------------------------------
/web/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | <%= prerenderPlaceholder %>
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/web/src/layouts/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/orta/redwood-object-identification/335ce11b67bc302d75145dc1e2db37bdaeee4736/web/src/layouts/.keep
--------------------------------------------------------------------------------
/web/src/layouts/UsersLayout/UsersLayout.tsx:
--------------------------------------------------------------------------------
1 | import { Link, routes } from "@redwoodjs/router"
2 | import { Toaster } from "@redwoodjs/web/toast"
3 |
4 | type UserLayoutProps = {
5 | children: React.ReactNode
6 | }
7 |
8 | const UsersLayout = ({ children }: UserLayoutProps) => {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 | Users
16 |
17 |
18 |
19 | +
New User
20 |
21 |
22 |
{children}
23 |
24 | )
25 | }
26 |
27 | export default UsersLayout
28 |
--------------------------------------------------------------------------------
/web/src/pages/FatalErrorPage/FatalErrorPage.tsx:
--------------------------------------------------------------------------------
1 | // This page will be rendered when an error makes it all the way to the top of the
2 | // application without being handled by a Javascript catch statement or React error
3 | // boundary.
4 | //
5 | // You can modify this page as you wish, but it is important to keep things simple to
6 | // avoid the possibility that it will cause its own error. If it does, Redwood will
7 | // still render a generic error page, but your users will prefer something a bit more
8 | // thoughtful. =)
9 |
10 | export default () => (
11 |
12 |
47 |
48 |
49 | Something went wrong
50 |
51 |
52 |
53 | )
54 |
--------------------------------------------------------------------------------
/web/src/pages/NotFoundPage/NotFoundPage.tsx:
--------------------------------------------------------------------------------
1 | export default () => (
2 |
3 |
38 |
39 |
40 | 404 Page Not Found
41 |
42 |
43 |
44 | )
45 |
--------------------------------------------------------------------------------
/web/src/pages/User/EditUserPage/EditUserPage.tsx:
--------------------------------------------------------------------------------
1 | import EditUserCell from "src/components/User/EditUserCell"
2 |
3 | type UserPageProps = {
4 | slug: string
5 | }
6 |
7 | const EditUserPage = ({ slug }: UserPageProps) => {
8 | return
9 | }
10 |
11 | export default EditUserPage
12 |
--------------------------------------------------------------------------------
/web/src/pages/User/NewUserPage/NewUserPage.tsx:
--------------------------------------------------------------------------------
1 | import NewUser from "src/components/User/NewUser"
2 |
3 | const NewUserPage = () => {
4 | return
5 | }
6 |
7 | export default NewUserPage
8 |
--------------------------------------------------------------------------------
/web/src/pages/User/UserPage/UserPage.tsx:
--------------------------------------------------------------------------------
1 | import UserCell from "src/components/User/UserCell"
2 |
3 | type UserPageProps = {
4 | slug: string
5 | }
6 |
7 | const UserPage = ({ slug }: UserPageProps) => {
8 | return
9 | }
10 |
11 | export default UserPage
12 |
--------------------------------------------------------------------------------
/web/src/pages/User/UsersPage/UsersPage.tsx:
--------------------------------------------------------------------------------
1 | import UsersCell from "src/components/User/UsersCell"
2 |
3 | const UsersPage = () => {
4 | return
5 | }
6 |
7 | export default UsersPage
8 |
--------------------------------------------------------------------------------
/web/src/scaffold.css:
--------------------------------------------------------------------------------
1 | /*
2 | normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css
3 | */
4 |
5 | .rw-scaffold *,
6 | .rw-scaffold ::after,
7 | .rw-scaffold ::before {
8 | box-sizing: inherit;
9 | }
10 | .rw-scaffold main {
11 | color: #4a5568;
12 | display: block;
13 | }
14 | .rw-scaffold h1,
15 | .rw-scaffold h2 {
16 | margin: 0;
17 | }
18 | .rw-scaffold a {
19 | background-color: transparent;
20 | }
21 | .rw-scaffold ul {
22 | margin: 0;
23 | padding: 0;
24 | }
25 | .rw-scaffold input {
26 | font-family: inherit;
27 | font-size: 100%;
28 | overflow: visible;
29 | }
30 | .rw-scaffold input:-ms-input-placeholder {
31 | color: #a0aec0;
32 | }
33 | .rw-scaffold input::-ms-input-placeholder {
34 | color: #a0aec0;
35 | }
36 | .rw-scaffold input::placeholder {
37 | color: #a0aec0;
38 | }
39 | .rw-scaffold table {
40 | border-collapse: collapse;
41 | }
42 |
43 | /*
44 | Style
45 | */
46 |
47 | .rw-scaffold,
48 | .rw-toast {
49 | background-color: #fff;
50 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
51 | 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji',
52 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
53 | }
54 | .rw-header {
55 | display: flex;
56 | justify-content: space-between;
57 | padding: 1rem 2rem 1rem 2rem;
58 | }
59 | .rw-main {
60 | margin-left: 1rem;
61 | margin-right: 1rem;
62 | padding-bottom: 1rem;
63 | }
64 | .rw-segment {
65 | border-radius: 0.5rem;
66 | overflow: hidden;
67 | width: 100%;
68 | }
69 | .rw-segment-header {
70 | background-color: #e2e8f0;
71 | color: #4a5568;
72 | padding: 0.75rem 1rem;
73 | }
74 | .rw-segment-main {
75 | background-color: #f7fafc;
76 | padding: 1rem;
77 | }
78 | .rw-link {
79 | color: #4299e1;
80 | text-decoration: underline;
81 | }
82 | .rw-link:hover {
83 | color: #2b6cb0;
84 | }
85 | .rw-forgot-link {
86 | font-size: 0.75rem;
87 | color: #a0aec0;
88 | text-align: right;
89 | margin-top: 0.1rem;
90 | }
91 | .rw-forgot-link:hover {
92 | font-size: 0.75rem;
93 | color: #4299e1;
94 | }
95 | .rw-heading {
96 | font-weight: 600;
97 | }
98 | .rw-heading.rw-heading-primary {
99 | font-size: 1.25rem;
100 | }
101 | .rw-heading.rw-heading-secondary {
102 | font-size: 0.875rem;
103 | }
104 | .rw-heading .rw-link {
105 | color: #4a5568;
106 | text-decoration: none;
107 | }
108 | .rw-heading .rw-link:hover {
109 | color: #1a202c;
110 | text-decoration: underline;
111 | }
112 | .rw-cell-error {
113 | font-size: 90%;
114 | font-weight: 600;
115 | }
116 | .rw-form-wrapper {
117 | box-sizing: border-box;
118 | font-size: 0.875rem;
119 | margin-top: -1rem;
120 | }
121 | .rw-cell-error,
122 | .rw-form-error-wrapper {
123 | padding: 1rem;
124 | background-color: #fff5f5;
125 | color: #c53030;
126 | border-width: 1px;
127 | border-color: #feb2b2;
128 | border-radius: 0.25rem;
129 | margin: 1rem 0;
130 | }
131 | .rw-form-error-title {
132 | margin-top: 0;
133 | margin-bottom: 0;
134 | font-weight: 600;
135 | }
136 | .rw-form-error-list {
137 | margin-top: 0.5rem;
138 | list-style-type: disc;
139 | list-style-position: inside;
140 | }
141 | .rw-button {
142 | border: none;
143 | color: #718096;
144 | cursor: pointer;
145 | display: flex;
146 | justify-content: center;
147 | font-size: 0.75rem;
148 | font-weight: 600;
149 | padding: 0.25rem 1rem;
150 | text-transform: uppercase;
151 | text-decoration: none;
152 | letter-spacing: 0.025em;
153 | border-radius: 0.25rem;
154 | line-height: 2;
155 | border: 0;
156 | }
157 | .rw-button:hover {
158 | background-color: #718096;
159 | color: #fff;
160 | }
161 | .rw-button.rw-button-small {
162 | font-size: 0.75rem;
163 | border-radius: 0.125rem;
164 | padding: 0.25rem 0.5rem;
165 | line-height: inherit;
166 | }
167 | .rw-button.rw-button-green {
168 | background-color: #48bb78;
169 | color: #fff;
170 | }
171 | .rw-button.rw-button-green:hover {
172 | background-color: #38a169;
173 | color: #fff;
174 | }
175 | .rw-button.rw-button-blue {
176 | background-color: #3182ce;
177 | color: #fff;
178 | }
179 | .rw-button.rw-button-blue:hover {
180 | background-color: #2b6cb0;
181 | }
182 | .rw-button.rw-button-red {
183 | background-color: #e53e3e;
184 | color: #fff;
185 | }
186 | .rw-button.rw-button-red:hover {
187 | background-color: #c53030;
188 | }
189 | .rw-button-icon {
190 | font-size: 1.25rem;
191 | line-height: 1;
192 | margin-right: 0.25rem;
193 | }
194 | .rw-button-group {
195 | display: flex;
196 | justify-content: center;
197 | margin: 0.75rem 0.5rem;
198 | }
199 | .rw-button-group .rw-button {
200 | margin: 0 0.25rem;
201 | }
202 | .rw-form-wrapper .rw-button-group {
203 | margin-top: 2rem;
204 | margin-bottom: 0;
205 | }
206 | .rw-label {
207 | display: block;
208 | margin-top: 1.5rem;
209 | color: #4a5568;
210 | font-weight: 600;
211 | }
212 | .rw-label.rw-label-error {
213 | color: #c53030;
214 | }
215 | .rw-input {
216 | display: block;
217 | margin-top: 0.5rem;
218 | width: 100%;
219 | padding: 0.5rem;
220 | border-width: 1px;
221 | border-style: solid;
222 | border-color: #e2e8f0;
223 | color: #4a5568;
224 | border-radius: 0.25rem;
225 | outline: none;
226 | }
227 | .rw-input[type='checkbox'],
228 | .rw-input[type='radio'] {
229 | width: 1rem;
230 | margin-left: 0;
231 | }
232 | .rw-input:focus {
233 | border-color: #a0aec0;
234 | }
235 | .rw-input-error {
236 | border-color: #c53030;
237 | color: #c53030;
238 | }
239 |
240 | .rw-input-error:focus {
241 | outline: none;
242 | border-color: #c53030;
243 | box-shadow: 0 0 5px #c53030;
244 | }
245 |
246 | .rw-field-error {
247 | display: block;
248 | margin-top: 0.25rem;
249 | font-weight: 600;
250 | text-transform: uppercase;
251 | font-size: 0.75rem;
252 | color: #c53030;
253 | }
254 | .rw-table-wrapper-responsive {
255 | overflow-x: scroll;
256 | }
257 | .rw-table-wrapper-responsive .rw-table {
258 | min-width: 48rem;
259 | }
260 | .rw-table {
261 | table-layout: auto;
262 | width: 100%;
263 | font-size: 0.875rem;
264 | }
265 | .rw-table th,
266 | .rw-table td {
267 | padding: 0.75rem;
268 | }
269 | .rw-table thead tr {
270 | background-color: #e2e8f0;
271 | color: #4a5568;
272 | }
273 | .rw-table th {
274 | font-weight: 600;
275 | text-align: left;
276 | }
277 | .rw-table thead th {
278 | text-align: left;
279 | }
280 | .rw-table tbody th {
281 | text-align: right;
282 | }
283 | @media (min-width: 768px) {
284 | .rw-table tbody th {
285 | width: 20%;
286 | }
287 | }
288 | .rw-table tbody tr {
289 | background-color: #f7fafc;
290 | border-top-width: 1px;
291 | }
292 | .rw-table tbody tr:nth-child(even) {
293 | background-color: #fff;
294 | }
295 | .rw-table input {
296 | margin-left: 0;
297 | }
298 | .rw-table-actions {
299 | display: flex;
300 | justify-content: flex-end;
301 | align-items: center;
302 | height: 17px;
303 | padding-right: 0.25rem;
304 | }
305 | .rw-table-actions .rw-button {
306 | background-color: transparent;
307 | }
308 | .rw-table-actions .rw-button:hover {
309 | background-color: #718096;
310 | color: #fff;
311 | }
312 | .rw-table-actions .rw-button-blue {
313 | color: #3182ce;
314 | }
315 | .rw-table-actions .rw-button-blue:hover {
316 | background-color: #3182ce;
317 | color: #fff;
318 | }
319 | .rw-table-actions .rw-button-red {
320 | color: #e53e3e;
321 | }
322 | .rw-table-actions .rw-button-red:hover {
323 | background-color: #e53e3e;
324 | color: #fff;
325 | }
326 | .rw-text-center {
327 | text-align: center;
328 | }
329 | .rw-login-container {
330 | display: flex;
331 | align-items: center;
332 | justify-content: center;
333 | width: 24rem;
334 | margin: 4rem auto;
335 | flex-wrap: wrap;
336 | }
337 | .rw-login-container .rw-form-wrapper {
338 | width: 100%;
339 | }
340 | .rw-login-link {
341 | margin-top: 1rem;
342 | color: #4a5568;
343 | font-size: 90%;
344 | text-align: center;
345 | flex-basis: 100%;
346 | }
347 |
--------------------------------------------------------------------------------
/web/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "noEmit": true,
4 | "allowJs": true,
5 | "esModuleInterop": true,
6 | "target": "esnext",
7 | "module": "esnext",
8 | "moduleResolution": "node",
9 | "baseUrl": "./",
10 | "rootDirs": [
11 | "./src",
12 | "../.redwood/types/mirror/web/src"
13 | ],
14 | "paths": {
15 | "src/*": [
16 | "./src/*",
17 | "../.redwood/types/mirror/web/src/*"
18 | ],
19 | "types/*": ["./types/*"],
20 | "@redwoodjs/testing": ["../node_modules/@redwoodjs/testing/web"]
21 | },
22 | "typeRoots": ["../node_modules/@types", "./node_modules/@types"],
23 | "types": ["jest"],
24 | "jsx": "preserve",
25 | },
26 | "include": [
27 | "src",
28 | "../.redwood/types/includes/all-*",
29 | "../.redwood/types/includes/web-*",
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------