11import { useNavigation , useLocalSearchParams , router } from "expo-router" ;
2- import React from "react" ;
2+ import React , { useEffect , useState } from "react" ;
33import {
44 View ,
55 Text ,
66 Image ,
77 SafeAreaView ,
88 ScrollView ,
99 TouchableOpacity ,
10+ Share ,
1011} from "react-native" ;
12+ import { supabase } from "../supabase" ;
1113
12- import { articles } from "@/app/constants/articlesDummy" ;
14+ interface Article {
15+ id : string ;
16+ title : string ;
17+ image : string ;
18+ category : string ;
19+ createdAt : string ;
20+ content : string [ ] ;
21+ author : string ;
22+ views : number ;
23+ }
1324
1425export default function ArticlesDetails ( ) {
1526 const navigation = useNavigation ( ) ;
16- const { id } = useLocalSearchParams ( ) ;
27+ const { id } = useLocalSearchParams < { id : string } > ( ) ;
28+ const [ loading , setLoading ] = useState < boolean > ( true ) ;
29+ const [ article , setArticle ] = useState < Article | null > ( null ) ;
30+ const [ error , setError ] = useState < string | null > ( null ) ;
31+ const [ bookmarked , setBookmarked ] = useState < boolean > ( false ) ;
32+ const [ userId , setUserId ] = useState < string | null > ( null ) ;
33+
34+ useEffect ( ( ) => {
35+ fetchUser ( ) ;
36+ } , [ ] ) ;
37+
38+ useEffect ( ( ) => {
39+ if ( article && userId ) {
40+ incrementViews ( article , userId ) ;
41+ }
42+ } , [ article , userId ] ) ;
43+
44+ useEffect ( ( ) => {
45+ if ( id && userId ) {
46+ fetchArticle ( ) ;
47+ } else if ( ! id ) {
48+ setLoading ( false ) ;
49+ setError ( "No article ID provided" ) ;
50+ }
51+ } , [ id , userId ] ) ;
52+
53+ const fetchUser = async ( ) => {
54+ const {
55+ data : { user } ,
56+ } = await supabase . auth . getUser ( ) ;
57+ if ( user ) {
58+ setUserId ( user . id ) ;
59+ } else {
60+ setError ( "User not logged in" ) ;
61+ setLoading ( false ) ;
62+ }
63+ } ;
64+
65+ const fetchArticle = async ( ) => {
66+ setLoading ( true ) ;
67+ setError ( null ) ;
68+
69+ const { data : article , error } = await supabase
70+ . from ( "articles" )
71+ . select ( "*" )
72+ . eq ( "id" , id )
73+ . single ( ) ;
74+
75+ if ( error ) {
76+ console . error ( "Error fetching article:" , error ) ;
77+ setError ( "Failed to load article." ) ;
78+ setArticle ( null ) ;
79+ } else if ( article ) {
80+ setArticle ( {
81+ ...article ,
82+ content : [
83+ article . content__001 ,
84+ article . content__002 ,
85+ article . content__003 ,
86+ ] . filter ( Boolean ) ,
87+ } ) ;
88+ checkIfBookmarked ( article . id ) ;
89+ } else {
90+ setArticle ( null ) ;
91+ }
92+ setLoading ( false ) ;
93+ } ;
94+
95+ const checkIfBookmarked = async ( articleId : string ) => {
96+ const { data, error } = await supabase
97+ . from ( "bookmarks" )
98+ . select ( "*" )
99+ . eq ( "user_id" , userId )
100+ . eq ( "article_id" , articleId )
101+ . single ( ) ;
102+
103+ if ( error ) {
104+ if ( error . code === "PGRST116" ) {
105+ setBookmarked ( false ) ;
106+ } else {
107+ console . log ( "Error checking bookmark:" , error ) ;
108+ }
109+ } else if ( data ) {
110+ setBookmarked ( true ) ;
111+ } else {
112+ setBookmarked ( false ) ;
113+ }
114+ } ;
115+
116+ const toggleBookmark = async ( ) => {
117+ if ( article ) {
118+ const isBookmarked = ! bookmarked ;
119+ console . log (
120+ `Toggling bookmark for article ID: ${ article . id } , new status: ${ isBookmarked } ` . toUpperCase ( )
121+ ) ;
122+
123+ if ( isBookmarked ) {
124+ const { data, error } = await supabase
125+ . from ( "bookmarks" )
126+ . insert ( [ { user_id : userId , article_id : article . id } ] ) ;
127+
128+ if ( error ) {
129+ console . error ( "Error adding bookmark:" , error ) ;
130+ } else {
131+ console . log ( "Bookmark added successfully" , data ) ;
132+ }
133+ } else {
134+ const { data, error } = await supabase
135+ . from ( "bookmarks" )
136+ . delete ( )
137+ . eq ( "user_id" , userId )
138+ . eq ( "article_id" , article . id ) ;
139+
140+ if ( error ) {
141+ console . error ( "Error removing bookmark:" , error ) ;
142+ } else {
143+ console . log ( "Bookmark removed successfully" , data ) ;
144+ }
145+ }
17146
18- const article = articles . find ( ( article ) => article . id === Number ( id ) ) ;
147+ setBookmarked ( isBookmarked ) ;
148+ }
149+ } ;
150+
151+ const incrementViews = async (
152+ article : Article ,
153+ userId : string
154+ ) : Promise < void > => {
155+ if ( ! article ?. id || ! userId ) {
156+ throw new Error ( "User ID or Article ID is undefined" ) ;
157+ }
158+
159+ try {
160+ // Check if the user has already viewed the article
161+ const { data : existingView , error : viewError } = await supabase
162+ . from ( "views" )
163+ . select ( "*" )
164+ . eq ( "user_id" , userId )
165+ . eq ( "article_id" , article . id )
166+ . single ( ) ;
167+
168+ if ( viewError && viewError . code !== "PGRST116" ) {
169+ // PGRST116: No rows returned
170+ throw viewError ;
171+ }
172+
173+ if ( ! existingView ) {
174+ // User has not viewed the article, so increment the views
175+ const { data, error } = await supabase . rpc ( "increment_views" , {
176+ article_id : article . id ,
177+ user_id : userId ,
178+ } ) ;
179+
180+ if ( error ) {
181+ console . error ( "Error incrementing views:" , error . message ) ;
182+ } else {
183+ console . log ( "Views incremented successfully" , data ) ;
184+ }
185+ } else {
186+ console . log ( "User already viewed this article" ) ;
187+ }
188+ } catch ( error ) {
189+ console . error ( "Error incrementing views:" , error ) ;
190+ }
191+ } ;
192+
193+ const shareArticle = ( ) => {
194+ if ( article ) {
195+ Share . share ( {
196+ message : `Check out this article: ${ article . title } ` ,
197+ url : article . image ,
198+ title : article . title ,
199+ } )
200+ . then ( ( result ) => console . log ( result ) )
201+ . catch ( ( error ) => console . log ( error ) ) ;
202+ }
203+ } ;
204+
205+ const more = ( ) => {
206+ console . log ( "more is clicked" ) ;
207+ } ;
208+
209+ if ( loading ) {
210+ return (
211+ < View style = { { flex : 1 , justifyContent : "center" , alignItems : "center" } } >
212+ < Text
213+ style = { { fontSize : 20 , fontFamily : "UrbanistBold" , color : "#212121" } }
214+ >
215+ Loading...
216+ </ Text >
217+ </ View >
218+ ) ;
219+ }
19220
20221 return (
21- < SafeAreaView style = { { paddingTop : 50 , backgroundColor : "white" , flex :1 } } >
222+ < SafeAreaView style = { { paddingTop : 50 , backgroundColor : "white" , flex : 1 } } >
22223 < View
23224 style = { {
24225 display : "flex" ,
25226 flexDirection : "row" ,
26227 justifyContent : "space-between" ,
27-
28228 } }
29229 >
30230 < View
@@ -35,9 +235,7 @@ export default function ArticlesDetails() {
35235 padding : 15 ,
36236 } }
37237 >
38- < TouchableOpacity
39- onPress = { ( ) => router . back ( ) }
40- >
238+ < TouchableOpacity onPress = { ( ) => router . back ( ) } >
41239 < Image
42240 style = { { marginTop : 10 } }
43241 source = { require ( "../../assets/articlesImages/vuuu.png" ) }
@@ -52,19 +250,23 @@ export default function ArticlesDetails() {
52250 padding : 15 ,
53251 } }
54252 >
55- < TouchableOpacity >
253+ < TouchableOpacity onPress = { toggleBookmark } >
56254 < Image
57255 style = { { height : 22.57 , width : 18.13 , padding : 10 } }
58- source = { require ( "../../assets/articlesImages/rwanda.png" ) }
256+ source = {
257+ bookmarked
258+ ? require ( "../../assets/articlesImages/Bookmarked.png" )
259+ : require ( "../../assets/articlesImages/rwanda.png" )
260+ }
59261 />
60262 </ TouchableOpacity >
61- < TouchableOpacity >
263+ < TouchableOpacity onPress = { shareArticle } >
62264 < Image
63265 style = { { height : 11.07 , width : 11.07 , padding : 10 } }
64266 source = { require ( "../../assets/articlesImages/rwiza.png" ) }
65267 />
66268 </ TouchableOpacity >
67- < TouchableOpacity >
269+ < TouchableOpacity onPress = { more } >
68270 < Image
69271 style = { { height : 1.17 , width : 1.17 , padding : 10 } }
70272 source = { require ( "../../assets/articlesImages/Group.png" ) }
@@ -82,19 +284,17 @@ export default function ArticlesDetails() {
82284 gap : 5 ,
83285 } }
84286 >
85- < TouchableOpacity >
86- { article ?. image && (
87- < Image
88- style = { {
89- height : 240 ,
90- width : "100%" ,
91- borderRadius : 24 ,
92- justifyContent : "center" ,
93- } }
94- source = { article ?. image }
95- />
96- ) }
97- </ TouchableOpacity >
287+ { article ?. image && (
288+ < Image
289+ style = { {
290+ height : 240 ,
291+ width : "100%" ,
292+ borderRadius : 24 ,
293+ justifyContent : "center" ,
294+ } }
295+ source = { { uri : article . image } }
296+ />
297+ ) }
98298 < View >
99299 < Text
100300 style = { {
@@ -115,17 +315,24 @@ export default function ArticlesDetails() {
115315 padding : 15 ,
116316 gap : 10 ,
117317 marginTop : 5 ,
318+ justifyContent : "space-between" ,
118319 } }
119320 >
120321 < Text
121322 style = { {
122- color : "#424242 " ,
323+ color : "#246BFD " ,
123324 fontSize : 10 ,
124- marginTop : 10 ,
325+ backgroundColor : "#E0E7FF" ,
326+ borderRadius : 6 ,
327+ height : 24 ,
328+ width : 59 ,
329+ textAlign : "center" ,
330+ padding : 5 ,
331+ marginTop : 5 ,
125332 fontFamily : "UrbanistRegular" ,
126333 } }
127334 >
128- { article ?. date }
335+ { article ?. category }
129336 </ Text >
130337 < Text
131338 style = { {
@@ -134,31 +341,31 @@ export default function ArticlesDetails() {
134341 backgroundColor : "#E0E7FF" ,
135342 borderRadius : 6 ,
136343 height : 24 ,
137- width : 59 ,
344+ width : "auto" ,
138345 textAlign : "center" ,
139346 padding : 5 ,
140347 marginTop : 5 ,
141348 fontFamily : "UrbanistRegular" ,
142349 } }
143350 >
144- { article ?. category }
351+ { article ?. author }
145352 </ Text >
146353 </ View >
147354 </ View >
148- { article ?. content . map ( ( content , index ) => (
149- < Text
150- key = { index }
151- style = { {
152- fontSize : 16 ,
153- color : "#424242" ,
154- fontFamily : "UrbanistRegular" ,
155- marginBottom : 20 ,
156- paddingHorizontal : 10 ,
157- } }
158- >
159- { content }
160- </ Text >
161- ) ) }
355+ { article ?. content . map ( ( section , index ) => (
356+ < Text
357+ key = { index }
358+ style = { {
359+ fontSize : 16 ,
360+ color : "#424242" ,
361+ fontFamily : "UrbanistRegular" ,
362+ marginBottom : 20 ,
363+ paddingHorizontal : 10 ,
364+ } }
365+ >
366+ { section }
367+ </ Text >
368+ ) ) }
162369 </ View >
163370 </ ScrollView >
164371 </ SafeAreaView >
0 commit comments