From bb9e688d1ef86b50d4bb6d359f58ee5d1f78e0d6 Mon Sep 17 00:00:00 2001 From: Zied Boughdir Date: Mon, 9 Mar 2026 11:53:44 +0100 Subject: [PATCH] Stars feature is fixed (#247) * Stars feature is fixed * feat: Initialize Supabase client with public credentials and define SkillStarData interface. --- apps/web-app/package-lock.json | 29 +++++++++------ apps/web-app/src/hooks/useSkillStars.ts | 47 +++++++++++-------------- apps/web-app/src/lib/supabase.ts | 20 ++++++----- 3 files changed, 52 insertions(+), 44 deletions(-) diff --git a/apps/web-app/package-lock.json b/apps/web-app/package-lock.json index ce9a31e3..e8fb590f 100644 --- a/apps/web-app/package-lock.json +++ b/apps/web-app/package-lock.json @@ -120,6 +120,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -469,6 +470,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -492,6 +494,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -1986,8 +1989,7 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -2124,6 +2126,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -2134,6 +2137,7 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -2272,6 +2276,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2322,7 +2327,6 @@ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -2472,6 +2476,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -2878,8 +2883,7 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/dunder-proto": { "version": "1.0.1", @@ -3056,6 +3060,7 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -4056,6 +4061,7 @@ "integrity": "sha512-MyL55p3Ut3cXbeBEG7Hcv0mVM8pp8PBNWxRqchZnSfAiES1v1mRnMeFfaHWIPULpwsYfvO+ZmMZz5tGCnjzDUQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cssstyle": "^4.0.1", "data-urls": "^5.0.0", @@ -4509,7 +4515,6 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -5646,6 +5651,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -5673,6 +5679,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -5705,7 +5712,6 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -5721,7 +5727,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -5774,6 +5779,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -5783,6 +5789,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -5795,8 +5802,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-markdown": { "version": "10.1.0", @@ -6639,6 +6645,7 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -7750,6 +7757,7 @@ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -7982,6 +7990,7 @@ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/apps/web-app/src/hooks/useSkillStars.ts b/apps/web-app/src/hooks/useSkillStars.ts index 6ee2c303..7583cbf8 100644 --- a/apps/web-app/src/hooks/useSkillStars.ts +++ b/apps/web-app/src/hooks/useSkillStars.ts @@ -65,7 +65,7 @@ export function useSkillStars(skillId: string | undefined): UseSkillStarsReturn .from('skill_stars') .select('star_count') .eq('skill_id', skillId) - .single(); + .maybeSingle(); if (!error && data) { setStarCount(data.star_count); @@ -103,36 +103,31 @@ export function useSkillStars(skillId: string | undefined): UseSkillStarsReturn // Sync to Supabase if available if (supabase) { - const { data: existingData, error: fetchError } = await supabase - .from('skill_stars') - .select('star_count') - .eq('skill_id', skillId) - .single(); - - if (fetchError && fetchError.code !== 'PGRST116') { - // PGRST116 = not found, which is expected for new skills - console.warn('Failed to fetch existing star count:', fetchError); - } - - if (existingData) { - // Update existing record - const { error: updateError } = await supabase + try { + // Fetch current count first + const { data: current } = await supabase .from('skill_stars') - .update({ star_count: existingData.star_count + 1 }) - .eq('skill_id', skillId); + .select('star_count') + .eq('skill_id', skillId) + .maybeSingle(); - if (updateError) { - console.warn('Failed to update star count:', updateError); - } - } else { - // Insert new record - const { error: insertError } = await supabase + const newCount = (current?.star_count || 0) + 1; + + // Upsert: insert or update in one call + const { error: upsertError } = await supabase .from('skill_stars') - .insert({ skill_id: skillId, star_count: 1 }); + .upsert( + { skill_id: skillId, star_count: newCount }, + { onConflict: 'skill_id' } + ); - if (insertError) { - console.warn('Failed to insert star count:', insertError); + if (upsertError) { + console.warn('Failed to upsert star count:', upsertError); + } else { + setStarCount(newCount); } + } catch (err) { + console.warn('Failed to sync star to Supabase:', err); } } } catch (error) { diff --git a/apps/web-app/src/lib/supabase.ts b/apps/web-app/src/lib/supabase.ts index 3124fcc4..c4a39b2a 100644 --- a/apps/web-app/src/lib/supabase.ts +++ b/apps/web-app/src/lib/supabase.ts @@ -1,14 +1,18 @@ import { createClient, SupabaseClient } from '@supabase/supabase-js' -const supabaseUrl = (import.meta as ImportMeta & { env: Record }).env.VITE_SUPABASE_URL -const supabaseAnonKey = (import.meta as ImportMeta & { env: Record }).env.VITE_SUPABASE_ANON_KEY +// Public Supabase credentials for the shared community stars database. +// The anon key is a public key by design — security is enforced via RLS policies. +// .env values override these defaults if provided. +const supabaseUrl = + (import.meta as ImportMeta & { env: Record }).env.VITE_SUPABASE_URL + || 'https://gczhgcbtjbvfrgfmpbmv.supabase.co' -// Create a single supabase client for interacting with your database -// Returns null if credentials are not configured (graceful degradation) -export const supabase: SupabaseClient | null = - supabaseUrl && supabaseAnonKey - ? createClient(supabaseUrl, supabaseAnonKey) - : null +const supabaseAnonKey = + (import.meta as ImportMeta & { env: Record }).env.VITE_SUPABASE_ANON_KEY + || 'sb_publishable_CyVwHGbtT80AuDFmXNkc9Q_YNcamTGg' + +// Create a single supabase client for interacting with the database +export const supabase: SupabaseClient = createClient(supabaseUrl, supabaseAnonKey) // Type for star data in the database export interface SkillStarData {