(
48 | ENDPOINT_CONFIGS.updateCurrentUser,
49 | {
50 | userName,
51 | firstName,
52 | lastName,
53 | }
54 | );
55 | }, [userName, firstName, lastName]);
56 |
57 | const handleSaveClick = async () => {
58 | try {
59 | await updateCurrentUser();
60 | setUserUpdateError('');
61 | refreshCurrentUser();
62 | setUserProfileMode(UserProfileMode.VIEWING);
63 | } catch (e) {
64 | setUserUpdateError((e as ApiError).message);
65 | }
66 | };
67 |
68 | const handleEditClick = () => {
69 | setUserProfileMode(UserProfileMode.EDITING);
70 | };
71 |
72 | const onEditOrSaveClick = async (e: FormEvent | MouseEvent) => {
73 | e.preventDefault();
74 | if (userProfileMode === UserProfileMode.EDITING) {
75 | await handleSaveClick();
76 | } else if (userProfileMode === UserProfileMode.VIEWING) {
77 | handleEditClick();
78 | }
79 | };
80 |
81 | if (isLoading) {
82 | return loading...
;
83 | }
84 |
85 | if (error) {
86 | return error loading user: {JSON.stringify(error)}
;
87 | }
88 |
89 | return (
90 |
91 |
92 |
93 | setUserName(e.target.value)}
98 | />
99 |
100 |
101 |
102 | setFirstName(e.target.value)}
107 | />
108 |
109 |
110 |
111 | setLastName(e.target.value)}
116 | />
117 |
118 | {isOwnProfile && (
119 |
128 | )}
129 |
130 | {!!userUpdateError && {userUpdateError}}
131 |
132 | );
133 | };
134 |
--------------------------------------------------------------------------------
/web/src/pages/view-post.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Button, Flex, Text, Textarea } from '@chakra-ui/react';
2 | import {
3 | CreateCommentRequest,
4 | CreateCommentResponse,
5 | ENDPOINT_CONFIGS,
6 | GetPostRequest,
7 | GetPostResponse,
8 | ListCommentsResponse,
9 | withParams,
10 | } from '@codersquare/shared';
11 | import { useQuery } from '@tanstack/react-query';
12 | import { useCallback, useState } from 'react';
13 | import { useParams } from 'react-router-dom';
14 |
15 | import { CommentCard } from '../components/comment-card';
16 | import { PostCard } from '../components/post-card';
17 | import { useDocumentTitle } from '../doc-title';
18 | import { callEndpoint } from '../fetch';
19 | import { isLoggedIn } from '../fetch/auth';
20 |
21 | export const ViewPost = () => {
22 | const { id: postId } = useParams();
23 | const {
24 | data,
25 | error,
26 | isLoading,
27 | refetch: refetchPost,
28 | } = useQuery(['viewpost'], () =>
29 | callEndpoint(withParams(ENDPOINT_CONFIGS.getPost, postId!))
30 | );
31 | const {
32 | data: commentsData,
33 | error: commentsError,
34 | isLoading: commentsLoading,
35 | refetch: refetchComments,
36 | } = useQuery(['listcomments'], () =>
37 | callEndpoint<{}, ListCommentsResponse>(withParams(ENDPOINT_CONFIGS.listComments, postId!))
38 | );
39 | const [comment, setComment] = useState('');
40 | const submitComment = useCallback(async () => {
41 | await callEndpoint(
42 | withParams(ENDPOINT_CONFIGS.createComment, postId!),
43 | { comment }
44 | );
45 | setComment('');
46 | refetchComments();
47 | }, [comment, postId, refetchComments]);
48 |
49 | const postname = isLoading ? 'Loading..' : error || !data ? 'Error' : data.post.title;
50 | useDocumentTitle(postname);
51 |
52 | if (isLoading) {
53 | return loading...
;
54 | }
55 |
56 | if (error || !data?.post) {
57 | return error loading post: {JSON.stringify(error)}
;
58 | }
59 |
60 | return (
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
106 |
107 | );
108 | };
109 |
--------------------------------------------------------------------------------
/web/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/web/src/routes.ts:
--------------------------------------------------------------------------------
1 | export const ROUTES = {
2 | HOME: '/',
3 | VIEW_POST: (pid: string) => `/p/${pid}`,
4 | SIGN_IN: '/signin',
5 | SIGN_UP: '/signup',
6 | NEW_POST: '/new',
7 | USER_PROFILE: (uid: string) => `/u/${uid}`,
8 | };
9 |
--------------------------------------------------------------------------------
/web/src/theme.tsx:
--------------------------------------------------------------------------------
1 | import { extendTheme, withDefaultColorScheme } from '@chakra-ui/react';
2 |
3 | export const theme = extendTheme(
4 | {
5 | colors: {
6 | brand: {
7 | 50: '#e4d2cc',
8 | 100: '#d9bdb4',
9 | 200: '#c9a093',
10 | 300: '#c08472',
11 | 400: '#a95b43',
12 | 500: '#92462f',
13 | 600: '#85361d',
14 | 700: '#6d230c',
15 | 800: '#4c1707',
16 | 900: '#310c01',
17 | },
18 | },
19 | },
20 | withDefaultColorScheme({ colorScheme: 'brand' })
21 | );
22 |
--------------------------------------------------------------------------------
/web/src/util.ts:
--------------------------------------------------------------------------------
1 | export const isDev =
2 | process.env.NODE_ENV === 'development' || window.location.hostname === 'localhost';
3 |
--------------------------------------------------------------------------------
/web/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "allowSyntheticDefaultImports": true,
9 | "strict": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "noFallthroughCasesInSwitch": true,
12 | "module": "esnext",
13 | "moduleResolution": "node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx"
18 | },
19 | "include": ["src"]
20 | }
21 |
--------------------------------------------------------------------------------