diff --git a/package-lock.json b/package-lock.json index 9a3e164..0715aeb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "bits-ui": "^0.21.7", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "dotenv": "^16.4.5", "drag-drop-touch": "^1.3.1", "firebase": "^10.11.1", "firebase-admin": "^12.1.0", @@ -2808,6 +2809,17 @@ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/drag-drop-touch": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/drag-drop-touch/-/drag-drop-touch-1.3.1.tgz", diff --git a/package.json b/package.json index ba71c69..a3056f9 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "bits-ui": "^0.21.7", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "dotenv": "^16.4.5", "drag-drop-touch": "^1.3.1", "firebase": "^10.11.1", "firebase-admin": "^12.1.0", diff --git a/src/lib/components/Myerror.svelte b/src/lib/components/Myerror.svelte new file mode 100644 index 0000000..90be279 --- /dev/null +++ b/src/lib/components/Myerror.svelte @@ -0,0 +1,10 @@ + +
+

403

+The Profile of @{username} is not public! +
+
+ forbidden +
diff --git a/src/lib/firebase.ts b/src/lib/firebase.ts new file mode 100644 index 0000000..4001218 --- /dev/null +++ b/src/lib/firebase.ts @@ -0,0 +1,101 @@ +// Import the functions you need from the SDKs you need +import { initializeApp } from "firebase/app"; +import { doc, getFirestore, onSnapshot } from "firebase/firestore"; +import { getAuth, onAuthStateChanged, type User } from "firebase/auth"; +import { getStorage } from "firebase/storage"; +import { writable, type Readable, derived } from "svelte/store"; +import { env } from "$env/dynamic/public"; + +// TODO: Add SDKs for Firebase products that you want to use +// https://firebase.google.com/docs/web/setup#available-libraries +// Your web app's Firebase configuration +const firebaseConfig = { + + apiKey: env.PUBLIC_API_KEY, + authDomain: env.PUBLIC_AUTH_DOMAIN , + projectId: env.PUBLIC_PROJECT_ID, + storageBucket: env.PUBLIC_STORAGE_BUCKET, + messagingSenderId: env.PUBLIC_MESSAGING_SENDER_ID, + appId: env.PUBLIC_APP_ID, + +}; + +// Initialize Firebase +const app = initializeApp(firebaseConfig); + +export const db = getFirestore(); +export const auth = getAuth(); +export const storage = getStorage(); + +/** + * @returns a store with the current firebase user + */ + +function userStore() { + let unsubscribe: () => void; + + if (!auth || !globalThis.window) { + console.warn("Auth is not initialized or not in browser"); + const { subscribe } = writable(null); + return { + subscribe, + }; + } + + const { subscribe } = writable(auth?.currentUser ?? null, (set) => { + unsubscribe = onAuthStateChanged(auth, (user) => { + set(user); + }); + + return () => unsubscribe(); + }); + + return { + subscribe, + }; +} + +export const user = userStore(); + +/** + * @param {string} path document path or reference + * @returns a store with realtime updates on document data + */ +export function docStore(path: string) { + let unsubscribe: () => void; + + const docRef = doc(db, path); + + const { subscribe } = writable(null, (set) => { + unsubscribe = onSnapshot(docRef, (snapshot) => { + set((snapshot.data() as T) ?? null); + }); + + return () => unsubscribe(); + }); + + return { + subscribe, + ref: docRef, + id: docRef.id, + }; +} + +interface UserData { + username: string; + bio: string; + photoURL: string; + links: any[]; + published: boolean; +} + +export const userData: Readable = derived( + user, + ($user, set) => { + if ($user) { + return docStore(`users/${$user.uid}`).subscribe(set); + } else { + set(null); + } + }, +); diff --git a/src/routes/[username]/+page.svelte b/src/routes/[username]/+page.svelte index 6eed820..06701d4 100644 --- a/src/routes/[username]/+page.svelte +++ b/src/routes/[username]/+page.svelte @@ -9,8 +9,10 @@ } from "firebase/auth"; import { goto } from "$app/navigation"; import { toasts, ToastContainer, FlatToast } from "svelte-toasts"; + import Myerror from "$lib/components/Myerror.svelte" export let data: PageData; + async function signInWithGoogle() { const provider = new GoogleAuthProvider(); const user = await signInWithPopup(auth, provider); @@ -36,6 +38,10 @@ navigator.clipboard.writeText(copytext) showToast() } + + + let unpublished = data.published + let username = data.username @@ -47,6 +53,11 @@ +{#if unpublished && data.username != $userData?.username} +
+ +
+{:else}

@{data.username} @@ -102,3 +113,4 @@ > {/if} +{/if} diff --git a/src/routes/[username]/+page.ts b/src/routes/[username]/+page.ts index 76332dc..03aba59 100644 --- a/src/routes/[username]/+page.ts +++ b/src/routes/[username]/+page.ts @@ -1,6 +1,6 @@ import { collection, getDocs, limit, query, where } from 'firebase/firestore'; import type { PageLoad } from './$types'; -import { db, userData } from '$lib/firebase'; +import { db, userData} from '$lib/firebase'; import { error } from '@sveltejs/kit'; export const load = (async ({ params }) => { @@ -20,14 +20,11 @@ export const load = (async ({ params }) => { throw error(404, "that user does not exists") } - if (!data.published) { - throw error(403, `The Profile of @${data.username} is not public!`) - } - return { username: data.username, photoURL: data.photoURL, bio: data.bio, links: data.links ?? [], + published: Boolean } }) satisfies PageLoad; diff --git a/static/403.png b/static/403.png new file mode 100644 index 0000000..4788a28 Binary files /dev/null and b/static/403.png differ diff --git a/tailwind.config.js b/tailwind.config.js index bbc0447..86e170b 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -2,67 +2,67 @@ import { fontFamily } from "tailwindcss/defaultTheme"; /** @type {import('tailwindcss').Config} */ const config = { - darkMode: ["class"], - content: ["./src/**/*.{html,js,svelte,ts}"], - plugins: [require("daisyui")], - daisyui: { - themes: ["dark"], + darkMode: ["class"], + content: ["./src/**/*.{html,js,svelte,ts}"], + plugins: [require("daisyui")], + daisyui: { + themes: ["dark"], + }, + safelist: ["dark"], + theme: { + container: { + center: true, + padding: "2rem", + screens: { + "2xl": "1400px", + }, }, - safelist: ["dark"], - theme: { - container: { - center: true, - padding: "2rem", - screens: { - "2xl": "1400px" - } - }, - extend: { - colors: { - border: "hsl(var(--border) / )", - input: "hsl(var(--input) / )", - ring: "hsl(var(--ring) / )", - background: "hsl(var(--background) / )", - foreground: "hsl(var(--foreground) / )", - primary: { - DEFAULT: "hsl(var(--primary) / )", - foreground: "hsl(var(--primary-foreground) / )" - }, - secondary: { - DEFAULT: "hsl(var(--secondary) / )", - foreground: "hsl(var(--secondary-foreground) / )" - }, - destructive: { - DEFAULT: "hsl(var(--destructive) / )", - foreground: "hsl(var(--destructive-foreground) / )" - }, - muted: { - DEFAULT: "hsl(var(--muted) / )", - foreground: "hsl(var(--muted-foreground) / )" - }, - accent: { - DEFAULT: "hsl(var(--accent) / )", - foreground: "hsl(var(--accent-foreground) / )" - }, - popover: { - DEFAULT: "hsl(var(--popover) / )", - foreground: "hsl(var(--popover-foreground) / )" - }, - card: { - DEFAULT: "hsl(var(--card) / )", - foreground: "hsl(var(--card-foreground) / )" - } - }, - borderRadius: { - lg: "var(--radius)", - md: "calc(var(--radius) - 2px)", - sm: "calc(var(--radius) - 4px)" - }, - fontFamily: { - sans: [...fontFamily.sans] - } - } - }, + extend: { + colors: { + border: "hsl(var(--border) / )", + input: "hsl(var(--input) / )", + ring: "hsl(var(--ring) / )", + background: "hsl(var(--background) / )", + foreground: "hsl(var(--foreground) / )", + primary: { + DEFAULT: "hsl(var(--primary) / )", + foreground: "hsl(var(--primary-foreground) / )", + }, + secondary: { + DEFAULT: "hsl(var(--secondary) / )", + foreground: "hsl(var(--secondary-foreground) / )", + }, + destructive: { + DEFAULT: "hsl(var(--destructive) / )", + foreground: "hsl(var(--destructive-foreground) / )", + }, + muted: { + DEFAULT: "hsl(var(--muted) / )", + foreground: "hsl(var(--muted-foreground) / )", + }, + accent: { + DEFAULT: "hsl(var(--accent) / )", + foreground: "hsl(var(--accent-foreground) / )", + }, + popover: { + DEFAULT: "hsl(var(--popover) / )", + foreground: "hsl(var(--popover-foreground) / )", + }, + card: { + DEFAULT: "hsl(var(--card) / )", + foreground: "hsl(var(--card-foreground) / )", + }, + }, + borderRadius: { + lg: "var(--radius)", + md: "calc(var(--radius) - 2px)", + sm: "calc(var(--radius) - 4px)", + }, + fontFamily: { + sans: [...fontFamily.sans], + }, + }, + }, }; export default config;