├── .gitignore
├── README.md
├── jsconfig.json
├── package-lock.json
├── package.json
├── postcss.config.cjs
├── src
├── app.css
├── app.html
├── global.d.ts
├── lib
│ ├── CreatePost.svelte
│ ├── Cubed.svelte
│ ├── Error.svelte
│ ├── Post.svelte
│ ├── UploadImage.svelte
│ ├── services.js
│ └── supabase.js
└── routes
│ ├── __layout.svelte
│ ├── index.svelte
│ └── login.svelte
├── static
└── favicon.png
├── svelte.config.js
└── tailwind.config.cjs
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Qwitter
2 |
3 | ## Disclaimer
4 | I didn't finish the README but please reference the video!
5 | [Video](https://www.youtube.com/watch?v=mPQyckogDYc)
6 |
7 | ## Made with
8 | - Sveltekit
9 | - Supabase
10 | - DaisyUI (TailwindCSS)
11 | ## Todo
12 | ### A. Setup Sveltekit project
13 | 1. `$ npm init svelte@next` (for prompt: Skeleton, no, no, no)
14 | 2. `$ npx svelte-add@latest tailwindcss`
15 | 3. `$ npm install`
16 | 4. `$ npm install daisyui`
17 | 5. Add `require('daisyui')` to `plugins` in `tailwind.config.cjs`
18 | (like: `plugins: [require('daisyui')],`)
19 | ### B. Setup Supabase project
20 | 1. Create new Project
21 | 2. Add src/lib/supabase.js file and add the supabase url and public key
22 | ```
23 | import { createClient } from '@supabase/supabase-js'
24 |
25 | const SUPABSE_URL = ''
26 | const SUPABASE_PUBLIC_KEY = ''
27 |
28 | const supabase = createClient(SUPABSE_URL, SUPABASE_PUBLIC_KEY)
29 | export default supabase
30 | ```
31 |
32 | ### C. Some Random Things..
33 | 1. (Optional) Disable need to Confirm Email under Authentication > Settings
34 | 2. (Optional) [Choose a theme](https://daisyui.com/docs/default-themes) and add it to src/app.html like ``
35 | 3. Add the following to your supabase.js file
36 | ```
37 | import {goto} from '$app/navigation'
38 | supabase.auth.onAuthStateChange((event) => {
39 | if (event === 'SIGNED_IN') {
40 | goto('/')
41 | } else if (event === 'SIGNED_OUT') {
42 | goto('/login')
43 | }
44 | })
45 | ```
46 |
47 | 4. Add a src/lib/Error.svelte component
48 | ```
49 |
52 |
53 | {#if error}
54 | {error.message}
55 | {/if}
56 | ```
57 |
58 | 4. Add a main.container to src/routes/__layout.svelte
59 | ```
60 |
61 |
62 |
63 | ```
64 |
65 | ### D. Setup Login Page
66 | 1. Add a src/lib/services.js file
67 | ```
68 | import supabase from './supabase'
69 |
70 | export function getUser() {
71 | return supabase.auth.user()
72 | }
73 |
74 | export async function signIn({email}) {
75 | const {error} = await supabase.auth
76 | .signIn({email})
77 | return {data: !error, error}
78 | }
79 |
80 | export async function signOut() {
81 | const {error} = await supabase.auth
82 | .signOut()
83 | return {data: !error, error}
84 | }
85 | ```
86 | 2. Add a src/routes/login.svelte page
87 | ```
88 |
97 | ```
98 | 3. Use an [Input with Button](https://daisyui.com/components/form/input) (scroll down) for the Magic Link
99 | 4. Add in form/promise logic to handle the signin (See video or login.svelte file)
100 |
101 | ### E. Add CreatePost.svelte
102 | 1. Create posts schema in Supabase (user, content)
103 | 2. Add to services.js
104 | ```
105 | export async function createPost({content, user}) { // user is user's email
106 | const {data, error} = await supabase
107 | .from('posts')
108 | .insert({content, user})
109 | return {data, error}
110 | }
111 | ```
112 | 3. Add src/lib/CreatePost.svelte (See video or CreatePost.svelte)
113 |
114 | ### F. Add Post.svelte
115 | 1. Add to services.js
116 | ```
117 | export async function createLike({post, user}) { // post is post's id
118 | const {data, error} = await supabase
119 | .from('likes')
120 | .insert({post, user})
121 | return {data, error}
122 | }
123 |
124 | export async function createComment({post, user, content}) {
125 | const {data, error} = await supabase
126 | .from('comments')
127 | .insert({post, user, content})
128 | return {data, error}
129 | }
130 | ```
131 | 2. Add src/lib/Post.svelte (See video or Post.svelte)
132 | 3. Use a [Card without Image](https://daisyui.com/components/card) (scroll down)
133 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "$lib": ["src/lib"],
6 | "$lib/*": ["src/lib/*"]
7 | }
8 | },
9 | "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
10 | }
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "qwitter",
3 | "version": "0.0.1",
4 | "scripts": {
5 | "dev": "svelte-kit dev",
6 | "build": "svelte-kit build",
7 | "package": "svelte-kit package",
8 | "preview": "svelte-kit preview"
9 | },
10 | "devDependencies": {
11 | "@sveltejs/adapter-auto": "next",
12 | "@sveltejs/kit": "next",
13 | "autoprefixer": "^10.3.7",
14 | "cssnano": "^5.0.8",
15 | "postcss": "^8.3.9",
16 | "postcss-load-config": "^3.1.0",
17 | "svelte": "^3.44.0",
18 | "svelte-preprocess": "^4.9.8",
19 | "tailwindcss": "^2.2.16"
20 | },
21 | "type": "module",
22 | "dependencies": {
23 | "@supabase/supabase-js": "^1.28.1",
24 | "daisyui": "^1.16.2",
25 | "svelte-cubed": "^0.2.1",
26 | "three": "^0.134.0"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | const tailwindcss = require("tailwindcss");
2 | const autoprefixer = require("autoprefixer");
3 | const cssnano = require("cssnano");
4 |
5 | const mode = process.env.NODE_ENV;
6 | const dev = mode === "development";
7 |
8 | const config = {
9 | plugins: [
10 | //Some plugins, like tailwindcss/nesting, need to run before Tailwind,
11 | tailwindcss(),
12 | //But others, like autoprefixer, need to run after,
13 | autoprefixer(),
14 | !dev &&
15 | cssnano({
16 | preset: "default",
17 | }),
18 | ],
19 | };
20 |
21 | module.exports = config;
22 |
--------------------------------------------------------------------------------
/src/app.css:
--------------------------------------------------------------------------------
1 | /* Write your global styles here, in PostCSS syntax */
2 | @tailwind base;
3 | @tailwind components;
4 | @tailwind utilities;
5 |
--------------------------------------------------------------------------------
/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | %svelte.head%
9 |
10 |
11 | %svelte.body%
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/global.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/lib/CreatePost.svelte:
--------------------------------------------------------------------------------
1 |
14 |
15 |
31 |
--------------------------------------------------------------------------------
/src/lib/Cubed.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 |
20 |
21 | {#if showMessage}
22 | Cool, huh? Now sign up!!!
23 | {/if}
--------------------------------------------------------------------------------
/src/lib/Error.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 | {#if error}
6 | {error.message}
7 | {/if}
--------------------------------------------------------------------------------
/src/lib/Post.svelte:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 | {#if publicURL}
27 |
28 |
29 |
30 | {/if}
31 |
32 |
{user} says..
33 |
{content}
34 |
35 | {#await createCommentPromise}
36 | Posting comment..
37 | {:then {data, error}}
38 |
39 | {#if data}
40 | Thanks for creating an insightful and kind comment!
41 | {/if}
42 |
48 | {/await}
49 |
50 |
51 | {#each comments as comment}
52 | {comment.user} says..
{comment.content}
53 | {/each}
54 |
55 |
--------------------------------------------------------------------------------
/src/lib/UploadImage.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
--------------------------------------------------------------------------------
/src/lib/services.js:
--------------------------------------------------------------------------------
1 | import supabase from './supabase'
2 |
3 | export function getUser() {
4 | return supabase.auth.user()
5 | }
6 |
7 | export async function signIn({email}) {
8 | const {error} = await supabase.auth
9 | .signIn({email})
10 | return {data: !error, error}
11 | }
12 |
13 | export async function signOut() {
14 | const {error} = await supabase.auth
15 | .signOut()
16 | return {data: !error, error}
17 | }
18 |
19 | export async function createPost({content, user, imageFile}) { // user is user's email
20 | if (imageFile) {
21 | const { data: imageData, error: imageError } = await supabase
22 | .storage
23 | .from('images')
24 | .upload(getUser().email + '/' + Date.now(), imageFile, {
25 | cacheControl: '3600',
26 | upsert: false
27 | })
28 | if (imageError) return {data: null, error: imageError}
29 | const {data, error} = await supabase
30 | .from('posts')
31 | .insert({content, user, image: imageData.Key})
32 |
33 | return {data, error}
34 | } else {
35 |
36 | const {data, error} = await supabase
37 | .from('posts')
38 | .insert({content, user})
39 | return {data, error}
40 | }
41 | }
42 |
43 |
44 | export async function createLike({post, user}) { // post is post's id
45 | const {data, error} = await supabase
46 | .from('likes')
47 | .insert({post, user})
48 | return {data, error}
49 | }
50 |
51 | export async function createComment({post, user, content}) {
52 | const {data, error} = await supabase
53 | .from('comments')
54 | .insert({post, user, content})
55 | return {data, error}
56 | }
57 |
58 | export async function getPosts() {
59 | let {data, error} = await supabase
60 | .from('posts')
61 | .select('*')
62 | .order('created_at', {ascending: false})
63 | .limit(5)
64 |
65 | if (error) return {data, error}
66 |
67 | data = await Promise.all(data.map(async (post) => {
68 | const [{count: likes,}, {data: comments, }, {publicURL}] = await Promise.all([
69 | await supabase
70 | .from('likes')
71 | .select('id', { count: 'estimated', head: true })
72 | .eq('post', post.id),
73 | await supabase
74 | .from('comments')
75 | .select('*')
76 | .eq('post', post.id),
77 | post.image ? await supabase.storage.from('images').getPublicUrl(post.image.split('/').slice(1).join('/')) : Promise.resolve({})
78 | ])
79 | // ERROR HANDLE!!!
80 | return {
81 | ...post, likes, comments, publicURL
82 | }
83 | }))
84 | return {data, error}
85 | }
--------------------------------------------------------------------------------
/src/lib/supabase.js:
--------------------------------------------------------------------------------
1 | import { createClient } from '@supabase/supabase-js'
2 |
3 | const SUPABSE_URL = 'https://sxkcwshulbsvwnlvjgfw.supabase.co'
4 | const SUPABASE_PUBLIC_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYzNzg3NzEzMSwiZXhwIjoxOTUzNDUzMTMxfQ.GHOELGasMrXrPyf-8ATREq9aR64tAb0J06Nz_SIOQAs'
5 |
6 | const supabase = createClient(SUPABSE_URL, SUPABASE_PUBLIC_KEY)
7 |
8 | export default supabase
--------------------------------------------------------------------------------
/src/routes/__layout.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/routes/index.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 |
33 |
34 |
35 |
36 | Welcome to Qwitter!
37 |
38 |
39 |
40 |
41 |
42 | {#each posts || [] as post}
43 |
44 | {:else}
45 | No posts found!
46 | {/each}
--------------------------------------------------------------------------------
/src/routes/login.svelte:
--------------------------------------------------------------------------------
1 |
19 |
20 | {#await signInPromise}
21 | Sending magic link to {email}
22 | {:then {data, error}}
23 |
24 | {#if data}
25 | Successfully sent magic link to {email}!
26 | {:else}
27 |
36 | {/if}
37 | {/await}
38 |
39 |
--------------------------------------------------------------------------------
/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sveltemaster/qwitter/1004730d2fdd9780754f87d7fa65468e2ce4a081/static/favicon.png
--------------------------------------------------------------------------------
/svelte.config.js:
--------------------------------------------------------------------------------
1 | import preprocess from "svelte-preprocess";
2 | import adapter from "@sveltejs/adapter-auto";
3 |
4 | /** @type {import('@sveltejs/kit').Config} */
5 | const config = {
6 | kit: {
7 | adapter: adapter(),
8 |
9 | // hydrate the element in src/app.html
10 | target: "#svelte",
11 | ssr: false
12 | },
13 |
14 | preprocess: [
15 | preprocess({
16 | postcss: true,
17 | }),
18 | ],
19 | };
20 |
21 | export default config;
22 |
--------------------------------------------------------------------------------
/tailwind.config.cjs:
--------------------------------------------------------------------------------
1 | const config = {
2 | mode: "jit",
3 | purge: ["./src/**/*.{html,js,svelte,ts}"],
4 |
5 | theme: {
6 | extend: {},
7 | },
8 |
9 | plugins: [
10 | require('daisyui')
11 | ],
12 | };
13 |
14 | module.exports = config;
15 |
--------------------------------------------------------------------------------