trae-bot commited on
Commit
df019aa
·
1 Parent(s): c6c1fa4

Update course detail page UI and logic for purchased state

Browse files
Files changed (1) hide show
  1. frontend/src/app/course/[id]/page.tsx +118 -23
frontend/src/app/course/[id]/page.tsx CHANGED
@@ -3,7 +3,7 @@
3
  import { useEffect, useState } from "react";
4
  import { useParams, useRouter } from "next/navigation";
5
  import { api } from "@/lib/api";
6
- import { ArrowLeft, CheckCircle } from "lucide-react";
7
  import Link from "next/link";
8
  import { useAuthStore } from "@/lib/store";
9
 
@@ -15,14 +15,26 @@ export default function CourseDetail() {
15
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
  const [course, setCourse] = useState<any>(null);
17
  const [loading, setLoading] = useState(true);
 
 
18
 
19
  useEffect(() => {
20
- const fetchCourse = async () => {
21
  try {
22
  const res = await api.get(`/api/courses/${id}`);
23
  if (res.success) {
24
  setCourse(res.data);
25
  }
 
 
 
 
 
 
 
 
 
 
26
  } catch (err) {
27
  console.error(err);
28
  } finally {
@@ -30,8 +42,8 @@ export default function CourseDetail() {
30
  }
31
  };
32
 
33
- fetchCourse();
34
- }, [id]);
35
 
36
  const handleBuy = async () => {
37
  if (!token) {
@@ -54,6 +66,29 @@ export default function CourseDetail() {
54
  }
55
  };
56
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  if (loading) {
58
  return <div className="animate-pulse h-96 bg-white rounded-xl"></div>;
59
  }
@@ -96,28 +131,88 @@ export default function CourseDetail() {
96
  <p className="whitespace-pre-line">{course.description}</p>
97
  </div>
98
 
99
- <div className="bg-gray-50 rounded-xl p-6 mb-8">
100
- <h4 className="font-semibold text-gray-900 mb-4">你将获得:</h4>
101
- <ul className="space-y-3">
102
- {['终身访问权限', '高质量学习资料', '支持手机和电视访问', '直达网盘链接'].map((item, i) => (
103
- <li key={i} className="flex items-center text-gray-600">
104
- <CheckCircle className="w-5 h-5 text-green-500 mr-3" />
105
- {item}
106
- </li>
107
- ))}
108
- </ul>
109
- </div>
110
-
111
- <div className="flex justify-end pt-6 border-t">
112
- <button
113
- onClick={handleBuy}
114
- className="bg-blue-600 text-white px-8 py-4 rounded-xl font-semibold text-lg hover:bg-blue-700 transition-colors shadow-lg shadow-blue-200"
115
- >
116
- 立即购买
117
- </button>
 
 
 
 
 
 
118
  </div>
119
  </div>
120
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  </div>
122
  );
123
  }
 
3
  import { useEffect, useState } from "react";
4
  import { useParams, useRouter } from "next/navigation";
5
  import { api } from "@/lib/api";
6
+ import { ArrowLeft, Link as LinkIcon, Copy, X } from "lucide-react";
7
  import Link from "next/link";
8
  import { useAuthStore } from "@/lib/store";
9
 
 
15
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
  const [course, setCourse] = useState<any>(null);
17
  const [loading, setLoading] = useState(true);
18
+ const [hasPurchased, setHasPurchased] = useState(false);
19
+ const [selectedLink, setSelectedLink] = useState<string | null>(null);
20
 
21
  useEffect(() => {
22
+ const fetchData = async () => {
23
  try {
24
  const res = await api.get(`/api/courses/${id}`);
25
  if (res.success) {
26
  setCourse(res.data);
27
  }
28
+
29
+ // 检查购买状态
30
+ if (token) {
31
+ const myCoursesRes = await api.get('/api/courses/my');
32
+ if (myCoursesRes.success) {
33
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
+ const isPurchased = myCoursesRes.data.some((c: any) => c.id === Number(id));
35
+ setHasPurchased(isPurchased);
36
+ }
37
+ }
38
  } catch (err) {
39
  console.error(err);
40
  } finally {
 
42
  }
43
  };
44
 
45
+ fetchData();
46
+ }, [id, token]);
47
 
48
  const handleBuy = async () => {
49
  if (!token) {
 
66
  }
67
  };
68
 
69
+ const handleAccess = async () => {
70
+ try {
71
+ const res = await api.get(`/api/courses/${id}/access`);
72
+ if (res.success && res.data.hasAccess) {
73
+ setSelectedLink(res.data.driveLink);
74
+ } else {
75
+ alert('获取资料失败或无权限');
76
+ }
77
+ } catch (err) {
78
+ console.error(err);
79
+ alert('获取资料失败');
80
+ }
81
+ };
82
+
83
+ const copyToClipboard = async (text: string) => {
84
+ try {
85
+ await navigator.clipboard.writeText(text);
86
+ alert('资料链接已复制到剪贴板');
87
+ } catch (err) {
88
+ console.error('Failed to copy!', err);
89
+ }
90
+ };
91
+
92
  if (loading) {
93
  return <div className="animate-pulse h-96 bg-white rounded-xl"></div>;
94
  }
 
131
  <p className="whitespace-pre-line">{course.description}</p>
132
  </div>
133
 
134
+ <div className="flex justify-end pt-6 border-t gap-4">
135
+ {hasPurchased ? (
136
+ <>
137
+ <button
138
+ disabled
139
+ className="bg-gray-100 text-gray-500 px-8 py-4 rounded-xl font-semibold text-lg cursor-not-allowed"
140
+ >
141
+ 已购买
142
+ </button>
143
+ <button
144
+ onClick={handleAccess}
145
+ className="bg-blue-600 text-white px-8 py-4 rounded-xl font-semibold text-lg hover:bg-blue-700 transition-colors shadow-lg shadow-blue-200 flex items-center"
146
+ >
147
+ <LinkIcon className="w-5 h-5 mr-2" />
148
+ 获取资料
149
+ </button>
150
+ </>
151
+ ) : (
152
+ <button
153
+ onClick={handleBuy}
154
+ className="bg-blue-600 text-white px-8 py-4 rounded-xl font-semibold text-lg hover:bg-blue-700 transition-colors shadow-lg shadow-blue-200"
155
+ >
156
+ 立即购买
157
+ </button>
158
+ )}
159
  </div>
160
  </div>
161
  </div>
162
+
163
+ {/* 资料链接弹窗 */}
164
+ {selectedLink && (
165
+ <div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4">
166
+ <div className="bg-white rounded-2xl max-w-lg w-full p-6 shadow-xl">
167
+ <div className="flex justify-between items-center mb-6">
168
+ <h3 className="text-xl font-bold text-gray-900">课程学习资料</h3>
169
+ <button
170
+ onClick={() => setSelectedLink(null)}
171
+ className="text-gray-400 hover:text-gray-600 transition-colors"
172
+ >
173
+ <X className="w-6 h-6" />
174
+ </button>
175
+ </div>
176
+
177
+ <div className="bg-blue-50 border border-blue-100 rounded-xl p-4 mb-6">
178
+ <p className="text-sm text-blue-800 mb-2">请复制以下链接在浏览器中打开,或直接点击访问:</p>
179
+ <div className="flex items-center gap-2">
180
+ <input
181
+ type="text"
182
+ readOnly
183
+ value={selectedLink}
184
+ className="flex-1 bg-white border border-blue-200 rounded-lg px-3 py-2 text-sm text-gray-700 outline-none"
185
+ />
186
+ <button
187
+ onClick={() => copyToClipboard(selectedLink)}
188
+ className="p-2 bg-blue-100 text-blue-700 rounded-lg hover:bg-blue-200 transition-colors"
189
+ title="复制链接"
190
+ >
191
+ <Copy className="w-4 h-4" />
192
+ </button>
193
+ </div>
194
+ </div>
195
+
196
+ <div className="flex gap-3 justify-end">
197
+ <button
198
+ onClick={() => setSelectedLink(null)}
199
+ className="px-5 py-2.5 border border-gray-200 text-gray-700 rounded-xl font-medium hover:bg-gray-50 transition-colors"
200
+ >
201
+ 关闭
202
+ </button>
203
+ <a
204
+ href={selectedLink}
205
+ target="_blank"
206
+ rel="noopener noreferrer"
207
+ className="px-5 py-2.5 bg-blue-600 text-white rounded-xl font-medium hover:bg-blue-700 transition-colors"
208
+ onClick={() => setSelectedLink(null)}
209
+ >
210
+ 直接访问
211
+ </a>
212
+ </div>
213
+ </div>
214
+ </div>
215
+ )}
216
  </div>
217
  );
218
  }