@riosst100/pwa-marketplace 3.1.2 → 3.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@riosst100/pwa-marketplace",
3
3
  "author": "riosst100@gmail.com",
4
- "version": "3.1.2",
4
+ "version": "3.1.3",
5
5
  "main": "src/index.js",
6
6
  "pwa-studio": {
7
7
  "targets": {
@@ -0,0 +1,151 @@
1
+ import React, { useMemo, useCallback, useState } from 'react';
2
+ import { Link, useLocation, useHistory } from 'react-router-dom';
3
+ import useHelpCenter from '@riosst100/pwa-marketplace/src/talons/HelpCenter/useHelpCenter';
4
+ import classes from './helpcenter.module.css';
5
+
6
+ const HelpCenter = () => {
7
+ const { loading, error, data } = useHelpCenter();
8
+ const history = useHistory();
9
+ const location = useLocation();
10
+
11
+ const queryParams = new URLSearchParams(location.search);
12
+ const activeTag = queryParams.get('tag') || '';
13
+
14
+ const tagCounts = useMemo(() => {
15
+ const counts = {};
16
+ (data.questions || []).forEach(q => {
17
+ const tags = Array.isArray(q.tags) ? q.tags : (typeof q.tags === 'string' ? q.tags.split(',') : []);
18
+ tags.map(t => t && t.trim()).filter(Boolean).forEach(t => {
19
+ counts[t] = (counts[t] || 0) + 1;
20
+ });
21
+ });
22
+ return counts;
23
+ }, [data.questions]);
24
+
25
+ const [expandedId, setExpandedId] = useState(null);
26
+ const toggleExpanded = useCallback((id) => {
27
+ setExpandedId(prev => prev === id ? null : id);
28
+ }, []);
29
+
30
+ const getExcerpt = useCallback((html, limit = 420) => {
31
+ if (!html) return { text: '', truncated: false };
32
+ const text = html
33
+ .replace(/<style[\s\S]*?<\/style>/gi, ' ')
34
+ .replace(/<script[\s\S]*?<\/script>/gi, ' ')
35
+ .replace(/<[^>]+>/g, ' ')
36
+ .replace(/&nbsp;/g, ' ')
37
+ .replace(/\s+/g, ' ')
38
+ .trim();
39
+ if (text.length <= limit) return { text, truncated: false };
40
+ const cut = text.slice(0, limit);
41
+ const lastSpace = cut.lastIndexOf(' ');
42
+ const snippet = (lastSpace > 0 ? cut.slice(0, lastSpace) : cut).trim() + '...';
43
+ return { text: snippet, truncated: true };
44
+ }, []);
45
+
46
+ const visibleQuestions = useMemo(() => {
47
+ const qs = data.questions || [];
48
+ if (!activeTag) return qs;
49
+ return qs.filter(q => {
50
+ const tags = Array.isArray(q.tags) ? q.tags : (typeof q.tags === 'string' ? q.tags.split(',') : []);
51
+ return tags.map(t => t && t.trim()).includes(activeTag);
52
+ });
53
+ }, [data.questions, activeTag]);
54
+
55
+ const onSelectTag = useCallback(tag => {
56
+ const params = new URLSearchParams(location.search);
57
+ if (tag) {
58
+ params.set('tag', tag);
59
+ } else {
60
+ params.delete('tag');
61
+ }
62
+ history.push({ pathname: location.pathname, search: params.toString() });
63
+ }, [history, location]);
64
+
65
+ if (loading) {
66
+ return <div>Loading Help Center…</div>;
67
+ }
68
+
69
+ if (error) {
70
+ return <div>We\'re sorry, an error has occurred while generating this content.</div>;
71
+ }
72
+
73
+ return (
74
+ <div className={classes.container}>
75
+ <h1 className={classes.title}>User Help Center</h1>
76
+ <div className={classes.grid}>
77
+ <aside className={classes.sidebar}>
78
+ <div className={classes.sidebarHeader}>TAGS</div>
79
+ <div className={classes.tagsWrap}>
80
+ {Object.keys(tagCounts).length === 0 && (
81
+ <span className={classes.emptyState}>No tags</span>
82
+ )}
83
+ {Object.entries(tagCounts).map(([tag, count]) => (
84
+ <button
85
+ key={tag}
86
+ onClick={() => onSelectTag(tag)}
87
+ className={`${classes.tagButton} ${activeTag === tag ? classes.tagButtonActive : ''}`}
88
+ >
89
+ {tag.toUpperCase()} ({count})
90
+ </button>
91
+ ))}
92
+ </div>
93
+ </aside>
94
+ <main className={classes.main}>
95
+ {activeTag && (
96
+ <h2 className={classes.tagTitle}>Tag : {activeTag}</h2>
97
+ )}
98
+ {visibleQuestions.map(q => {
99
+ const isOpen = expandedId === q.question_id;
100
+ return (
101
+ <article key={q.question_id} className={classes.questionCard}>
102
+ <header
103
+ onClick={() => toggleExpanded(q.question_id)}
104
+ className={`${classes.questionHeader} ${isOpen ? classes.questionHeaderOpen : ''}`}
105
+ >
106
+ <span className={classes.plusIcon}>{isOpen ? '−' : '+'}</span>
107
+ {q.title}
108
+ </header>
109
+ {isOpen && (
110
+ <section className={classes.questionBody}>
111
+ {q.answer ? (
112
+ (() => {
113
+ const { text, truncated } = getExcerpt(q.answer);
114
+ return (
115
+ <>
116
+ <p className={classes.excerpt}>{text} {truncated && (
117
+ <Link to={`/help-center/question/${q.question_id}`} className={classes.tagLink}>Read more</Link>
118
+ )}</p>
119
+ </>
120
+ );
121
+ })()
122
+ ) : (
123
+ <div>We\'re sorry, an error has occurred while generating this content.</div>
124
+ )}
125
+ <div className={classes.published}>
126
+ <small>
127
+ {q.update_time ? `on ${new Date(q.update_time).toLocaleDateString()} ` : ''}
128
+ Published in: {Array.isArray(q.tags) ? (
129
+ q.tags.map((t, idx) => (
130
+ <Link key={idx} to={`/help-center?tag=${encodeURIComponent(t)}`} className={classes.tagLink}>{t}{idx < q.tags.length - 1 ? ', ' : ''}</Link>
131
+ ))
132
+ ) : (
133
+ <Link to={`/help-center?tag=${encodeURIComponent(q.tags)}`} className={classes.tagLink}>{q.tags}</Link>
134
+ )}
135
+ </small>
136
+ </div>
137
+ </section>
138
+ )}
139
+ </article>
140
+ );
141
+ })}
142
+ {visibleQuestions.length === 0 && (
143
+ <div className={classes.emptyState}>No questions found.</div>
144
+ )}
145
+ </main>
146
+ </div>
147
+ </div>
148
+ );
149
+ };
150
+
151
+ export default HelpCenter;
@@ -0,0 +1,225 @@
1
+ .container {}
2
+
3
+ .grid {
4
+ display: grid;
5
+ grid-template-columns: 280px 1fr;
6
+ gap: 24px;
7
+ }
8
+
9
+ .title {
10
+ margin-top: 16px;
11
+ font-size: 17px;
12
+ font-weight: 700;
13
+ margin-bottom: 16px;
14
+ }
15
+
16
+ .detailTitle {
17
+ margin-top: 8px;
18
+ margin-bottom: 16px;
19
+ font-size: 32px;
20
+ line-height: 1.25;
21
+ font-weight: 700;
22
+ }
23
+
24
+ .sidebar {
25
+ border: 1px solid #E6E9EA;
26
+ border-radius: 6px;
27
+ max-height: max-content;
28
+ }
29
+
30
+ .sidebarHeader {
31
+ padding: 12px;
32
+ border-bottom: 1px solid #E6E9EA;
33
+ font-weight: 600;
34
+ background-color: #F2F2F2;
35
+ }
36
+
37
+ .tagsWrap {
38
+ padding: 12px;
39
+ display: flex;
40
+ flex-wrap: wrap;
41
+ gap: 8px;
42
+ }
43
+
44
+ .tagButton {
45
+ border: 1px solid #E6E9EA;
46
+ padding: 8px 10px;
47
+ background: #F8F8F8;
48
+ color: #f76b1c;
49
+ cursor: pointer;
50
+ }
51
+
52
+ .tagButtonActive {
53
+ background: #fff3e6;
54
+ }
55
+
56
+ .main {}
57
+
58
+ .tagTitle {
59
+ margin: 0;
60
+ margin-bottom: 16px;
61
+ }
62
+
63
+ .questionCard {
64
+ border: 1px solid #E6E9EA;
65
+ border-radius: 6px;
66
+ margin-bottom: 16px;
67
+ }
68
+
69
+ .questionCardDetail {
70
+ border-radius: 6px;
71
+ margin-bottom: 16px;
72
+ }
73
+
74
+ .questionHeader {
75
+ padding: 12px;
76
+ background: #F2F2F2;
77
+ color: #f76b1c;
78
+ font-weight: 700;
79
+ display: flex;
80
+ align-items: center;
81
+ gap: 8px;
82
+ cursor: pointer;
83
+ }
84
+
85
+ .questionHeaderOpen {
86
+ background: #fff3e6;
87
+ }
88
+
89
+ .plusIcon {
90
+ display: inline-flex;
91
+ align-items: center;
92
+ justify-content: center;
93
+ width: 18px;
94
+ height: 18px;
95
+ border-radius: 3px;
96
+ background: #f76b1c;
97
+ color: #fff;
98
+ font-size: 14px;
99
+ line-height: 18px;
100
+ }
101
+
102
+ .questionBody {
103
+ padding: 16px 12px;
104
+ color: #292D32;
105
+ font-size: 16px;
106
+ line-height: 1.7;
107
+ }
108
+ .container{
109
+ margin: 3rem 0px;
110
+ }
111
+ .excerpt {
112
+ color: #292D32;
113
+ }
114
+
115
+ .published {
116
+ margin-top: 8px;
117
+ color: #999999;
118
+ }
119
+
120
+ .tagLink {
121
+ color: #f76b1c;
122
+ }
123
+ .emptyState {
124
+ color: #999999;
125
+ }
126
+
127
+ .tagsLine {
128
+ margin-top: 16px;
129
+ display: flex;
130
+ align-items: center;
131
+ gap: 20px;
132
+ flex-wrap: wrap;
133
+ }
134
+
135
+ .tagPill {
136
+ display: inline-flex;
137
+ align-items: center;
138
+ gap: 8px;
139
+ padding: 4px 10px;
140
+ border-radius: 0px 4px 4px 0px;
141
+ background: #292D32;
142
+ color: #fff;
143
+ font-size: 12px;
144
+ position: relative;
145
+ text-transform: uppercase;
146
+ }
147
+
148
+ .tagPill::before {
149
+ content: '';
150
+ position: absolute;
151
+ left: -13px;
152
+ top: 50%;
153
+ transform: translateY(-53%);
154
+ width: 0;
155
+ height: 0;
156
+ border-top: 13px solid transparent;
157
+ border-bottom: 13px solid transparent;
158
+ border-right: 14px solid #292D32;
159
+ }
160
+
161
+ .tagPill::after {
162
+ content: '';
163
+ position: absolute;
164
+ left: -7px;
165
+ top: 50%;
166
+ transform: translateY(-50%);
167
+ width: 5px;
168
+ height: 5px;
169
+ background: #fff;
170
+ border-radius: 50%;
171
+ }
172
+
173
+ /* Mobile adjustments */
174
+ @media (max-width: 768px) {
175
+ .container {
176
+ margin: 1.5rem 0;
177
+ }
178
+ .grid {
179
+ grid-template-columns: 1fr;
180
+ gap: 16px;
181
+ }
182
+ .sidebar {
183
+ border-radius: 8px;
184
+ }
185
+ .tagsWrap {
186
+ gap: 6px;
187
+ }
188
+ .tagButton {
189
+ padding: 8px 12px;
190
+ border-radius: 6px;
191
+ }
192
+ .title {
193
+ font-size: 18px;
194
+ margin-bottom: 12px;
195
+ }
196
+ .tagTitle {
197
+ font-size: 16px;
198
+ margin-bottom: 12px;
199
+ }
200
+ .questionCard {
201
+ margin-bottom: 12px;
202
+ }
203
+ .questionHeader {
204
+ padding: 12px;
205
+ font-size: 14px;
206
+ }
207
+ .plusIcon {
208
+ width: 16px;
209
+ height: 16px;
210
+ font-size: 12px;
211
+ line-height: 16px;
212
+ }
213
+ .questionBody {
214
+ padding: 12px;
215
+ font-size: 15px;
216
+ line-height: 1.6;
217
+ }
218
+ .tagsLine {
219
+ gap: 18px;
220
+ }
221
+ .tagPill {
222
+ font-size: 11px;
223
+ padding: 4px 8px;
224
+ }
225
+ }
@@ -0,0 +1 @@
1
+ export { default } from './helpCenter';
@@ -0,0 +1,89 @@
1
+ import React, { useMemo } from 'react';
2
+ import { Link, useParams } from 'react-router-dom';
3
+ import useHelpCenter from '@riosst100/pwa-marketplace/src/talons/HelpCenter/useHelpCenter';
4
+ import classes from './helpcenter.module.css';
5
+
6
+ const QuestionDetail = () => {
7
+ const { questionId } = useParams();
8
+ const { loading, error, data } = useHelpCenter();
9
+
10
+ const tagCounts = useMemo(() => {
11
+ const counts = {};
12
+ (data.questions || []).forEach(q => {
13
+ const tags = Array.isArray(q.tags) ? q.tags : (typeof q.tags === 'string' ? q.tags.split(',') : []);
14
+ tags.map(t => t && t.trim()).filter(Boolean).forEach(t => {
15
+ counts[t] = (counts[t] || 0) + 1;
16
+ });
17
+ });
18
+ return counts;
19
+ }, [data.questions]);
20
+
21
+ const question = useMemo(() => {
22
+ const id = Number(questionId);
23
+ return (data.questions || []).find(q => Number(q.question_id) === id) || null;
24
+ }, [data.questions, questionId]);
25
+
26
+ if (loading) return <div>Loading…</div>;
27
+ if (error) return <div>We\'re sorry, an error has occurred while generating this content.</div>;
28
+ if (!question) return <div className={classes.emptyState}>Question not found.</div>;
29
+
30
+ return (
31
+ <div className={classes.container}>
32
+ <div className={classes.grid}>
33
+ <aside className={classes.sidebar}>
34
+ <div className={classes.sidebarHeader}>TAGS</div>
35
+ <div className={classes.tagsWrap}>
36
+ {Object.keys(tagCounts).length === 0 && (
37
+ <span className={classes.emptyState}>No tags</span>
38
+ )}
39
+ {Object.entries(tagCounts).map(([tag, count]) => (
40
+ <Link key={tag} to={`/help-center?tag=${encodeURIComponent(tag)}`} className={`${classes.tagButton}`}>
41
+ {tag.toUpperCase()} ({count})
42
+ </Link>
43
+ ))}
44
+ </div>
45
+ </aside>
46
+ <main>
47
+ <h1 className={classes.detailTitle}>{question.title}</h1>
48
+ <article className={classes.questionCardDetail}>
49
+ <section className={classes.questionBody}>
50
+ {question.answer ? (
51
+ <div dangerouslySetInnerHTML={{ __html: question.answer }} />
52
+ ) : (
53
+ <div>We\'re sorry, an error has occurred while generating this content.</div>
54
+ )}
55
+ <div className={classes.published}>
56
+ <p>
57
+ {question.update_time ? `on ${new Date(question.update_time).toLocaleDateString()} ` : ''}
58
+ Published in: {Array.isArray(question.tags) ? (
59
+ question.tags.map((t, idx) => (
60
+ <Link key={idx} to={`/help-center?tag=${encodeURIComponent(t)}`} className={classes.tagLink}>{t}{idx < question.tags.length - 1 ? ', ' : ''}</Link>
61
+ ))
62
+ ) : (
63
+ <Link to={`/help-center?tag=${encodeURIComponent(question.tags)}`} className={classes.tagLink}>{question.tags}</Link>
64
+ )}
65
+ </p>
66
+ </div>
67
+ <div className={classes.tagsLine}>
68
+ <strong>TAGS:</strong>
69
+ {Array.isArray(question.tags) ? (
70
+ question.tags.map((t, idx) => (
71
+ <span key={idx} className={classes.tagPill}>
72
+ {String(t).toUpperCase()}
73
+ </span>
74
+ ))
75
+ ) : (
76
+ <span className={classes.tagPill}>
77
+ {String(question.tags).toUpperCase()}
78
+ </span>
79
+ )}
80
+ </div>
81
+ </section>
82
+ </article>
83
+ </main>
84
+ </div>
85
+ </div>
86
+ );
87
+ };
88
+
89
+ export default QuestionDetail;
package/src/intercept.js CHANGED
@@ -87,6 +87,20 @@ module.exports = targets => {
87
87
  authed: true,
88
88
  redirectTo: "/sign-in"
89
89
  },
90
+ {
91
+ exact: true,
92
+ name: "HelpCenter",
93
+ pattern: "/help-center",
94
+ path: require.resolve("./components/HelpCenter/index.js"),
95
+ authed: false,
96
+ },
97
+ {
98
+ exact: true,
99
+ name: "HelpCenterQuestionDetail",
100
+ pattern: "/help-center/question/:questionId",
101
+ path: require.resolve("./components/HelpCenter/questionDetail.js"),
102
+ authed: false,
103
+ },
90
104
  {
91
105
  exact: true,
92
106
  name: "VerifyEmailPage",
@@ -10,7 +10,7 @@ const getToKnowUsLinks = new Map()
10
10
  .set('About TCG Collective', null)
11
11
  .set('Career', '/')
12
12
  .set('Contact Us', '/')
13
- .set('Help Center', '/')
13
+ .set('Help Center', '/help-center')
14
14
  .set('Intelectual Property Claims', '/');
15
15
 
16
16
  const aboutLinks = new Map()
@@ -0,0 +1,93 @@
1
+ import { gql } from '@apollo/client';
2
+
3
+ export const FAQ_CATEGORIES = gql`
4
+ query faqCategories {
5
+ faqCategories {
6
+ body_background
7
+ border_color
8
+ border_radius
9
+ border_width
10
+ category_id
11
+ creation_time
12
+ description
13
+ icon
14
+ is_active
15
+ margin_bottom
16
+ margin_left
17
+ meta_description
18
+ meta_keywords
19
+ page_title
20
+ parent_id
21
+ position
22
+ seller_id
23
+ status
24
+ store_id
25
+ title
26
+ title_background
27
+ title_color
28
+ title_size
29
+ update_time
30
+ }
31
+ }
32
+ `;
33
+
34
+ export const FAQ_QUESTIONS = gql`
35
+ query faqQuestions {
36
+ faqQuestions {
37
+ _first_store_id
38
+ animation_class
39
+ animation_speed
40
+ answer
41
+ author_email
42
+ author_name
43
+ body_bg
44
+ body_color
45
+ body_size
46
+ border_width
47
+ creation_time
48
+ disklike
49
+ is_active
50
+ is_featured
51
+ like
52
+ meta_description
53
+ meta_keywords
54
+ page_title
55
+ question_active_icon
56
+ question_icon
57
+ question_id
58
+ question_margin
59
+ question_position
60
+ question_type
61
+ question_url
62
+ store_code
63
+ store_id
64
+ tags
65
+ title
66
+ title_bg
67
+ title_bg_active
68
+ title_border_color
69
+ title_border_radius
70
+ title_color
71
+ title_color_active
72
+ title_size
73
+ update_time
74
+ }
75
+ }
76
+ `;
77
+
78
+ export const FAQ_TAGS = gql`
79
+ query faqTags {
80
+ faqTags {
81
+ alias
82
+ name
83
+ question_id
84
+ tag_id
85
+ }
86
+ }
87
+ `;
88
+
89
+ export default {
90
+ faqCategoriesQuery: FAQ_CATEGORIES,
91
+ faqQuestionsQuery: FAQ_QUESTIONS,
92
+ faqTagsQuery: FAQ_TAGS
93
+ };
@@ -0,0 +1,59 @@
1
+ import { useMemo } from 'react';
2
+ import { useIntl } from 'react-intl';
3
+ import { useQuery } from '@apollo/client';
4
+ import mergeOperations from '@magento/peregrine/lib/util/shallowMerge';
5
+ import DEFAULT_OPERATIONS from './helpCenter.gql';
6
+
7
+ export const useHelpCenter = (props = {}) => {
8
+ const { formatMessage } = useIntl();
9
+
10
+ const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations);
11
+
12
+ const {
13
+ faqCategoriesQuery,
14
+ faqQuestionsQuery,
15
+ faqTagsQuery
16
+ } = operations;
17
+
18
+ const {
19
+ data: categoriesData,
20
+ loading: categoriesLoading,
21
+ error: categoriesError
22
+ } = useQuery(faqCategoriesQuery, {
23
+ fetchPolicy: 'cache-and-network'
24
+ });
25
+
26
+ const {
27
+ data: questionsData,
28
+ loading: questionsLoading,
29
+ error: questionsError
30
+ } = useQuery(faqQuestionsQuery, {
31
+ fetchPolicy: 'cache-and-network'
32
+ });
33
+
34
+ const {
35
+ data: tagsData,
36
+ loading: tagsLoading,
37
+ error: tagsError
38
+ } = useQuery(faqTagsQuery, {
39
+ fetchPolicy: 'cache-and-network'
40
+ });
41
+
42
+ const loading = categoriesLoading || questionsLoading || tagsLoading;
43
+ const error = categoriesError || questionsError || tagsError || null;
44
+
45
+ const data = useMemo(() => ({
46
+ categories: categoriesData?.faqCategories || [],
47
+ questions: questionsData?.faqQuestions || [],
48
+ tags: tagsData?.faqTags || []
49
+ }), [categoriesData, questionsData, tagsData]);
50
+
51
+ return {
52
+ loading,
53
+ error,
54
+ data,
55
+ formatMessage
56
+ };
57
+ };
58
+
59
+ export default useHelpCenter;