diff --git a/README.md b/README.md index 9afc742da..9d3ba19fe 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,80 @@ -# css-frameworks-ca -Replace this text with a description of your social media project. +ChatterHub - A Social Media Platform Frontend + +Project Overview + +ChatterHub is a social media platform frontend built using HTML, CSS/SCSS, and JavaScript. The platform allows users to register, log in, and view a feed of posts retrieved from the Noroff API. Key features include user authentication, post management, and a visually structured content feed. + +Features + + • User Authentication + • Register with valid @noroff.no or @stud.noroff.no emails. + • Login functionality with secure token storage. + • Post Feed + • Displays posts fetched from the Noroff API in a responsive card grid. + • Pagination with a 9-post display limit. + • Fallback image for posts without media. + +Getting Started + +Prerequisites + + • Visual Studio Code or any preferred IDE + • Basic understanding of JavaScript, HTML, and CSS + • API key from the Noroff API + +Installation + + 1. Clone the repository: +git clone +cd chatterhub + 2. Open in Visual Studio Code: +code . + 3. Install Live Server for local development: + • Search for “Live Server” in the VSCode extensions tab and install it. + +Usage + + 1. Start the project using Live Server. + 2. Navigate to the following routes: + • login/index.html for login + • register/index.html for registration + • feed/index.html to view the post feed + +Project Structure + +chatterhub/ +│ +├── index.html # Home page +├── login/ +│ └── index.html # Login page +├── register/ +│ └── index.html # Registration page +├── feed/ +│ └── index.html # Post feed page +├── src/ # Source files +│ └── scss/ # SCSS styles +├── scripts/ # JavaScript files +│ └── login.js # Login handling script +│ └── register.js # Registration script +│ └── posts.js # Post feed handling +└── images/ # Static images + +API Integration + +Noroff API + +The application integrates with the Noroff API to fetch and display posts as well as handle user authentication. To access the API, make sure to use your valid API key and Authorization Bearer token. + +Project Management + +📌 Trello Board: [JavaScript 2 Course Assignment](https://trello.com/b/VYdUkY8J/javascript-2-course-assignment) + +Development Notes + + • SCSS files are precompiled for cleaner CSS management. + • Responsive Bootstrap grid is used for structured layout design. + • Authenticated routes require valid access tokens. + +License + +This project is licensed under the MIT License. \ No newline at end of file diff --git a/feed/index.html b/feed/index.html new file mode 100644 index 000000000..856becc13 --- /dev/null +++ b/feed/index.html @@ -0,0 +1,150 @@ + + + + + + ChatterHub - Profile + + + + + + + + + +
+
+
+ +
+ Profile Picture +
Feed
+
+ + +
+ +
+ + +
+ +
+ + +
+
+

Create a New Post

+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+
+
+ + +

Posts

+
+
+
+ + + +
+ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/images/C Logo.png b/images/C Logo.png new file mode 100644 index 000000000..6631562be Binary files /dev/null and b/images/C Logo.png differ diff --git a/images/Default_pfp.svg.png b/images/Default_pfp.svg.png new file mode 100644 index 000000000..f8b690afb Binary files /dev/null and b/images/Default_pfp.svg.png differ diff --git a/images/default_image.png b/images/default_image.png new file mode 100644 index 000000000..81eb65297 Binary files /dev/null and b/images/default_image.png differ diff --git a/index.html b/index.html new file mode 100644 index 000000000..aec39ece9 --- /dev/null +++ b/index.html @@ -0,0 +1,96 @@ + + + + + + ChatterHub - Login or Register + + + + + + + + +
+
+
+ +
+ ChatterHub Logo +
Welcome to ChatterHub
+
+ +

Connect with your friends and the world around you.

+ +
+
+

Login

+ +
+ +
+ + +
+ Please enter an email. +
+
+ +
+ + +
+ Password must be at least 8 characters long. +
+
+ +
+ + +
+
+ +

+
+
+ + +
+
+
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..09124432e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,520 @@ +{ + "name": "css-frameworks-ca", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "css-frameworks-ca", + "version": "0.0.1", + "license": "ISC", + "dependencies": { + "bootstrap": "^5.2.3" + }, + "devDependencies": { + "sass": "^1.80.3" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.4.1.tgz", + "integrity": "sha512-HNjmfLQEVRZmHRET336f20H/8kOozUGwk7yajvsonjNxbj2wBTK1WsQuHkD5yYh9RxFGL2EyDHryOihOwUoKDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.4.1", + "@parcel/watcher-darwin-arm64": "2.4.1", + "@parcel/watcher-darwin-x64": "2.4.1", + "@parcel/watcher-freebsd-x64": "2.4.1", + "@parcel/watcher-linux-arm-glibc": "2.4.1", + "@parcel/watcher-linux-arm64-glibc": "2.4.1", + "@parcel/watcher-linux-arm64-musl": "2.4.1", + "@parcel/watcher-linux-x64-glibc": "2.4.1", + "@parcel/watcher-linux-x64-musl": "2.4.1", + "@parcel/watcher-win32-arm64": "2.4.1", + "@parcel/watcher-win32-ia32": "2.4.1", + "@parcel/watcher-win32-x64": "2.4.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.4.1.tgz", + "integrity": "sha512-LOi/WTbbh3aTn2RYddrO8pnapixAziFl6SMxHM69r3tvdSm94JtCenaKgk1GRg5FJ5wpMCpHeW+7yqPlvZv7kg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.4.1.tgz", + "integrity": "sha512-ln41eihm5YXIY043vBrrHfn94SIBlqOWmoROhsMVTSXGh0QahKGy77tfEywQ7v3NywyxBBkGIfrWRHm0hsKtzA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.4.1.tgz", + "integrity": "sha512-yrw81BRLjjtHyDu7J61oPuSoeYWR3lDElcPGJyOvIXmor6DEo7/G2u1o7I38cwlcoBHQFULqF6nesIX3tsEXMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.4.1.tgz", + "integrity": "sha512-TJa3Pex/gX3CWIx/Co8k+ykNdDCLx+TuZj3f3h7eOjgpdKM+Mnix37RYsYU4LHhiYJz3DK5nFCCra81p6g050w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.4.1.tgz", + "integrity": "sha512-4rVYDlsMEYfa537BRXxJ5UF4ddNwnr2/1O4MHM5PjI9cvV2qymvhwZSFgXqbS8YoTk5i/JR0L0JDs69BUn45YA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.4.1.tgz", + "integrity": "sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.4.1.tgz", + "integrity": "sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.4.1.tgz", + "integrity": "sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.4.1.tgz", + "integrity": "sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.4.1.tgz", + "integrity": "sha512-Uq2BPp5GWhrq/lcuItCHoqxjULU1QYEcyjSO5jqqOK8RNFDBQnenMMx4gAl3v8GiWa59E9+uDM7yZ6LxwUIfRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.4.1.tgz", + "integrity": "sha512-maNRit5QQV2kgHFSYwftmPBxiuK5u4DXjbXx7q6eKjq5dsLXZ4FJiVvlcw35QXzk0KrUecJmuVFbj4uV9oYrcw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.4.1.tgz", + "integrity": "sha512-+DvS92F9ezicfswqrvIRM2njcYJbd5mb9CUgtrHCHmvn7pPPa+nMDRu1o1bYYz/l5IB2NVGNJWiH7h1E58IF2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/bootstrap": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.3.tgz", + "integrity": "sha512-cEKPM+fwb3cT8NzQZYEu4HilJ3anCrWqh3CHAok1p9jXqMPsPTBhU25fBckEJHJ/p+tTxTFTsFQGM+gaHpi3QQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "license": "MIT", + "peerDependencies": { + "@popperjs/core": "^2.11.6" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/immutable": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", + "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/readdirp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/sass": { + "version": "1.80.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.80.3.tgz", + "integrity": "sha512-ptDWyVmDMVielpz/oWy3YP3nfs7LpJTHIJZboMVs8GEC9eUmtZTZhMHlTW98wY4aEorDfjN38+Wr/XjskFWcfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/watcher": "^2.4.1", + "chokidar": "^4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + } + } +} diff --git a/package.json b/package.json index 4086f0f7b..fc4078913 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,10 @@ "version": "0.0.1", "description": "A starting point for the CSS Frameworks CA project", "scripts": { - "test": "echo \"We will learn more about testing in the Workflow course\" && exit 1" + "test": "echo \"We will learn more about testing in the Workflow course\" && exit 1", + "build": "sass src/scss:dist/css --style=expanded", + "sass": "sass src/index.scss dist/css/index.css --watch", + "start": "live-server" }, "repository": { "type": "git", @@ -22,5 +25,11 @@ "bugs": { "url": "https://github.com/NoroffFEU/css-frameworks-ca/issues" }, - "homepage": "https://github.com/NoroffFEU/css-frameworks-ca#readme" + "homepage": "https://github.com/NoroffFEU/css-frameworks-ca#readme", + "dependencies": { + "bootstrap": "^5.2.3" + }, + "devDependencies": { + "sass": "^1.80.3" + } } diff --git a/profile/index.html b/profile/index.html new file mode 100644 index 000000000..7952b2784 --- /dev/null +++ b/profile/index.html @@ -0,0 +1,151 @@ + + + + + + ChatterHub - Profile + + + + + + + + + +
+
+
+ +
+ Profile Picture +
Your Profile
+
+ +
+
+

Profile Details

+
+ +
+ + +
+ Profile Image +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ Password must be at least 8 characters long. +
+
+ +
+ +
+
+
+
+ + +
+
+
Social Stats
+ + +
+
+
Followers
+

1,250

+
+
+
Following
+

350

+
+
+ + +
+
Bio
+

Short description or bio about the user goes here.

+
+
+
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/register/index.html b/register/index.html new file mode 100644 index 000000000..fbbce205c --- /dev/null +++ b/register/index.html @@ -0,0 +1,95 @@ + + + + + + ChatterHub - Login or Register + + + + + + + + +
+
+
+ +
+ ChatterHub Logo +
Register to ChatterHub
+
+ +

Connect with your friends and the world around you.

+ +
+
+

Register

+ +
+ +
+ + +
+ Please enter a valid email. +
+
+ +
+ + +
+ Password must be at least 8 characters long. +
+
+ +
+ + +
+
+

+
+
+ + +
+
+
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/scripts/login.js b/scripts/login.js new file mode 100644 index 000000000..eb8fe9b8f --- /dev/null +++ b/scripts/login.js @@ -0,0 +1,37 @@ +// Handle form submission +document.getElementById('auth-form').addEventListener('submit', async (event) => { + event.preventDefault(); // Prevent form from refreshing the page + + const email = document.getElementById('email').value; + const password = document.getElementById('password').value; + + try { + const response = await fetch('https://v2.api.noroff.dev/auth/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Noroff-API-Key': '486ac6ab-b456-4770-bd5e-b4ea0f8a7582', + }, + body: JSON.stringify({ email, password }), + }); + + if (!response.ok) { + throw new Error('Login failed: Invalid email or password'); + } + + const result = await response.json(); + console.log('Login successful:', result); + + // Store user info in localStorage + localStorage.setItem('accessToken', result.data.accessToken); + localStorage.setItem('user', JSON.stringify(result.data)); // Store full user details + + console.log('User data stored:', result.data); + + // Redirect to profile page + window.location.href = 'profile/index.html'; + } catch (error) { + // Display error message + document.getElementById('error-message').innerText = error.message; + } +}); \ No newline at end of file diff --git a/scripts/posts.js b/scripts/posts.js new file mode 100644 index 000000000..f14575889 --- /dev/null +++ b/scripts/posts.js @@ -0,0 +1,258 @@ +const options = { + headers: { + 'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiTGVpa2VuIiwiZW1haWwiOiJEYW5TdHIxNjIyMUBzdHVkLm5vcm9mZi5ubyIsImlhdCI6MTczNjg2MjU0NX0.6jkhKB8V-xHdJcFJ6cso8nCUTa_xgV2DJT4knWnoqJ0', + 'X-Noroff-API-Key': 'f1174243-8934-4994-8987-80c3aa38f4a9', + }, +}; + +let currentPostId = null; + +// Fetch and display posts +async function fetchPosts(sortBy = 'newest', searchQuery = '') { + try { + let url = 'https://v2.api.noroff.dev/social/posts?_author=true'; + const params = new URLSearchParams(); + + if (searchQuery) { + url = 'https://v2.api.noroff.dev/social/posts/search'; + params.set('q', searchQuery); + } + + // Sorting logic + if (sortBy) { + switch (sortBy) { + case 'newest': + params.set('sort', 'created'); + params.set('sortOrder', 'desc'); + break; + case 'oldest': + params.set('sort', 'created'); + params.set('sortOrder', 'asc'); + break; + case 'title-asc': + params.set('sort', 'title'); + params.set('sortOrder', 'asc'); + break; + case 'title-desc': + params.set('sort', 'title'); + params.set('sortOrder', 'desc'); + break; + } + } + + url += `?${params.toString()}`; + + const response = await fetch(url, options); + if (!response.ok) { + throw new Error(`Error fetching posts: ${response.statusText}`); + } + + const posts = await response.json(); + if (Array.isArray(posts)) { + renderPosts(posts); + } else if (posts.data) { + renderPosts(posts.data); + } else { + renderPosts([]); + } + } catch (error) { + console.error(error.message); + document.getElementById('post-feed').innerText = 'Failed to load posts. Please try again.'; + } +} + +function renderPosts(posts) { + const postFeed = document.getElementById('post-feed'); + postFeed.innerHTML = ''; + + let row = document.createElement('div'); + row.className = 'row'; + + const fallbackImage = '/images/default_image.png'; + + posts.forEach((post, index) => { + const postElement = document.createElement('div'); + postElement.className = 'col-12 col-md-6 col-lg-4 mb-4'; + + const mediaContent = post.media?.url + ? `${post.media.alt}` + : `Fallback Image`; + + postElement.innerHTML = ` +
+ ${mediaContent} +
+
${post.title}
+

${post.body}

+ Read More +
+
+ `; + + row.appendChild(postElement); + + if ((index + 1) % 3 === 0 || index === posts.length - 1) { + postFeed.appendChild(row); + row = document.createElement('div'); + row.className = 'row'; + } + }); + + document.querySelectorAll('.read-more-button').forEach(button => { + button.addEventListener('click', function (event) { + const postId = this.getAttribute('data-post-id'); + viewPostById(postId, event); + }); + }); +} + +async function viewPostById(postId, event) { + event.preventDefault(); + currentPostId = postId; + + try { + const url = `https://v2.api.noroff.dev/social/posts/${postId}?_author=true`; + const response = await fetch(url, options); + if (!response.ok) { + throw new Error(`Error fetching post details: ${response.statusText}`); + } + + const post = await response.json(); + + document.getElementById('post-details-title').innerText = post.data.title; + document.getElementById('post-details-body').innerText = post.data.body; + document.getElementById('post-details-created').innerText = `Created: ${new Date(post.data.created).toLocaleDateString()}`; + document.getElementById('post-details-tags').innerText = `Tags: ${post.data.tags.join(', ')}`; + document.getElementById('post-details-author').innerText = `Author: ${post.data.author?.name || 'Unknown'}`; + document.getElementById('post-details-image').src = post.data.media?.url || '/images/default_image.png'; + + const modal = new bootstrap.Modal(document.getElementById('post-details-modal')); + modal.show(); + } catch (error) { + console.error(error.message); + document.getElementById('error-message').innerText = 'Failed to load post details. Please try again.'; + } +} + +// Edit Post +async function editPost() { + const newTitle = prompt('Enter the new title:'); + const newBody = prompt('Enter the new body content:'); + if (!newTitle || !newBody) { + alert('Title and body content are required.'); + return; + } + + try { + const updatedPost = { + title: newTitle, + body: newBody, + }; + + const response = await fetch(`https://v2.api.noroff.dev/social/posts/${currentPostId}`, { + method: 'PUT', + headers: { + ...options.headers, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(updatedPost), + }); + + if (!response.ok) { + throw new Error('You can only edit your own posts.'); + } + + alert('Post updated successfully!'); + fetchPosts(); + } catch (error) { + console.error(error.message); + alert('You can only edit your own posts.'); + } +} + +// Delete Post +async function deletePost() { + if (!confirm('Are you sure you want to delete this post?')) return; + + try { + const response = await fetch(`https://v2.api.noroff.dev/social/posts/${currentPostId}`, { + method: 'DELETE', + headers: options.headers, + }); + + if (!response.ok) { + throw new Error('You can only delete your own posts.'); + } + + alert('Post deleted successfully!'); + fetchPosts(); + } catch (error) { + console.error(error.message); + alert('You can only delete your own posts.'); + } +} + +// Add event listener for "Edit" and "Delete" buttons +document.getElementById('edit-post').addEventListener('click', editPost); +document.getElementById('delete-post').addEventListener('click', deletePost); + +document.getElementById('sort-filter').addEventListener('change', function () { + const sortBy = this.value; + const searchQuery = document.getElementById('search-input').value; + fetchPosts(sortBy, searchQuery); +}); + +document.getElementById('search-input').addEventListener('input', function () { + const searchQuery = this.value; + const sortBy = document.getElementById('sort-filter').value; + fetchPosts(sortBy, searchQuery); +}); + +// Handle post creation and assign the logged-in user as the author +document.getElementById('post-form').addEventListener('submit', async function (event) { + event.preventDefault(); + + const formData = new FormData(this); + const postData = { + title: formData.get('post-title'), + body: formData.get('post-content'), + }; + + const imageUrl = formData.get('post-image-url'); + if (imageUrl) { + postData.media = { url: imageUrl }; + } + + // Get the logged-in user's name from the access token + const accessToken = document.cookie + .split('; ') + .find(row => row.startsWith('accessToken')) + ?.split('=')[1]; + + if (accessToken) { + const decodedToken = JSON.parse(atob(accessToken.split('.')[1])); + postData.author = { name: decodedToken.name }; // Assigning the logged-in user as the author + } + + try { + const response = await fetch('https://v2.api.noroff.dev/social/posts', { + method: 'POST', + headers: { + ...options.headers, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(postData), + }); + + if (!response.ok) { + throw new Error(`Error creating post: ${response.statusText}`); + } + + document.getElementById('post-form').reset(); + fetchPosts(); + } catch (error) { + console.error(error.message); + } +}); + +fetchPosts(); \ No newline at end of file diff --git a/scripts/register.js b/scripts/register.js new file mode 100644 index 000000000..6edcc0841 --- /dev/null +++ b/scripts/register.js @@ -0,0 +1,56 @@ +const options = { + headers: { + 'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiTGVpa2VuIiwiZW1haWwiOiJEYW5TdHIxNjIyMUBzdHVkLm5vcm9mZi5ubyIsImlhdCI6MTczNDYxMjUwMX0.kdqFQFoJrAEbfiBvonCs3fC5Muc_fnzgf56Tt_8Nf8w', + 'X-Noroff-API-Key': '486ac6ab-b456-4770-bd5e-b4ea0f8a7582' + } + }; + + // Handle click on the register button + document.getElementById('auth-form').addEventListener('submit', async (event) => { + event.preventDefault(); // Prevent form from refreshing the page + + const email = document.getElementById('email').value; + const password = document.getElementById('password').value; + + // Validate email to ensure it's a valid @stud.noroff.no email + if (!email.match(/^[a-zA-Z0-9._%+-]+@stud\.noroff\.no$/)) { + document.getElementById('error-message').innerText = 'Please enter a valid @stud.noroff.no email.'; + return; + } + + // Validate password length + if (password.length < 8) { + document.getElementById('error-message').innerText = 'Password must be at least 8 characters long.'; + return; + } + + try { + const response = await fetch('https://v2.api.noroff.dev/auth/register', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiTGVpa2VuIiwiZW1haWwiOiJEYW5TdHIxNjIyMUBzdHVkLm5vcm9mZi5ubyIsImlhdCI6MTczNDYxMjUwMX0.kdqFQFoJrAEbfiBvonCs3fC5Muc_fnzgf56Tt_8Nf8w', + 'X-Noroff-API-Key': '486ac6ab-b456-4770-bd5e-b4ea0f8a7582', + }, + body: JSON.stringify({ + email, + password, + }), + }); + + if (!response.ok) { + throw new Error('Registration failed: Invalid input or email already exists'); + } + + const result = await response.json(); + + // Save access token to localStorage for authenticated requests + localStorage.setItem('accessToken', result.data.accessToken); + + // Redirect to profile page + window.location.href = '/profile/index.html'; + } catch (error) { + // Display error message + document.getElementById('error-message').innerText = error.message; + } + }); \ No newline at end of file diff --git a/src/scss/index.css b/src/scss/index.css new file mode 100644 index 000000000..f66374c4b --- /dev/null +++ b/src/scss/index.css @@ -0,0 +1,109 @@ +@import url("https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap"); +body { + background-color: #FFFFFF; + font-family: "Montserrat", sans-serif; + color: #333333; + margin: 0; + padding: 0; +} + +.navbar { + background-color: #F7F7F7; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); +} + +.auth-header { + font-size: 2.5rem; + font-weight: bold; + text-align: center; + color: #333333; + margin-bottom: 20px; +} + +.navbar { + background-color: #FFFFFF; + border-bottom: 2px solid #D94E48; + transition: background-color 0.3s ease; +} +.navbar.shadow-sm { + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} +.navbar .navbar-brand { + font-size: 1.5rem; +} +.navbar .nav-link { + color: #333333; + font-weight: 500; + margin-left: 15px; + transition: color 0.3s; +} +.navbar .nav-link:hover { + color: #c52f29; +} +.navbar .dropdown-item { + color: #333333; +} +.navbar .dropdown-item:hover { + background-color: #D94E48; + color: white; +} + +.tagline { + text-align: center; + color: #A59BB0; + margin-bottom: 30px; +} + +.auth-container { + margin-top: 50px; +} + +.card { + border-radius: 8px; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); +} + +.form-label { + font-weight: bold; +} + +input[type=text], +input[type=password] { + border-radius: 4px; +} + +.btn { + transition: background-color 0.3s ease; +} + +.btn-primary { + background-color: #D94E48; + border: none; +} + +.btn-primary:hover { + background-color: #c52f29; +} + +.btn-outline-secondary { + color: #A59BB0; + border: 2px solid #A59BB0; +} +.btn-outline-secondary:hover { + background-color: #A59BB0; + color: white; +} + +.footer-links { + text-align: center; + margin-top: 20px; + padding-bottom: 20px; +} +.footer-links a { + color: #A59BB0; + text-decoration: none; + margin: 0 10px; +} +.footer-links a:hover { + text-decoration: underline; +}/*# sourceMappingURL=index.css.map */ \ No newline at end of file diff --git a/src/scss/index.css.map b/src/scss/index.css.map new file mode 100644 index 000000000..cadcfd6fc --- /dev/null +++ b/src/scss/index.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["index.scss","index.css"],"names":[],"mappings":"AACQ,4FAAA;AAWR;EACI,yBAPe;EAQf,qCALU;EAMV,cAPS;EAQT,SAAA;EACA,UAAA;ACVJ;;ADcA;EACI,yBAfc;EAgBd,wCAAA;ACXJ;;ADeA;EACI,iBAAA;EACA,iBAAA;EACA,kBAAA;EACA,cAvBS;EAwBT,mBAAA;ACZJ;;ADcA;EACI,yBA7Be;EA8Bf,gCAAA;EACA,sCAAA;ACXJ;ADaI;EACI,wCAAA;ACXR;ADcI;EACI,iBAAA;ACZR;ADeI;EACI,cAxCK;EAyCL,gBAAA;EACA,iBAAA;EACA,sBAAA;ACbR;ADeQ;EACI,cAAA;ACbZ;ADiBI;EACI,cAnDK;ACoCb;ADiBQ;EACI,yBA1DI;EA2DJ,YAAA;ACfZ;;ADqBA;EACI,kBAAA;EACA,cAlEc;EAmEd,mBAAA;AClBJ;;ADsBA;EACI,gBAAA;ACnBJ;;ADuBA;EACI,kBAAA;EACA,yCAAA;ACpBJ;;ADwBA;EACI,iBAAA;ACrBJ;;ADwBA;;EAEI,kBAAA;ACrBJ;;ADyBA;EACI,sCAAA;ACtBJ;;ADyBA;EACI,yBAlGY;EAmGZ,YAAA;ACtBJ;;ADyBA;EACI,yBAAA;ACtBJ;;ADyBA;EACI,cA1Gc;EA2Gd,yBAAA;ACtBJ;ADwBI;EACI,yBA9GU;EA+GV,YAAA;ACtBR;;AD4BA;EACI,kBAAA;EACA,gBAAA;EACA,oBAAA;ACzBJ;AD2BI;EACI,cA3HU;EA4HV,qBAAA;EACA,cAAA;ACzBR;AD2BQ;EACI,0BAAA;ACzBZ","file":"index.css"} \ No newline at end of file diff --git a/src/scss/index.scss b/src/scss/index.scss index 8b1378917..6d8985618 100644 --- a/src/scss/index.scss +++ b/src/scss/index.scss @@ -1 +1,137 @@ +// Font Import +@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap'); +// Variables for colors and fonts +$primary-color: #D94E48; +$secondary-color: #A59BB0; +$background-color: #FFFFFF; +$card-background: #F7F7F7; +$text-color: #333333; +$font-family: 'Montserrat', sans-serif; + +// Global styles +body { + background-color: $background-color; + font-family: $font-family; + color: $text-color; + margin: 0; + padding: 0; +} + +// Navbar styles +.navbar { + background-color: $card-background; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); +} + +// Header styles +.auth-header { + font-size: 2.5rem; + font-weight: bold; + text-align: center; + color: $text-color; + margin-bottom: 20px; +} +.navbar { + background-color: $background-color; + border-bottom: 2px solid $primary-color; + transition: background-color 0.3s ease; + + &.shadow-sm { + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + } + + .navbar-brand { + font-size: 1.5rem; + } + + .nav-link { + color: $text-color; + font-weight: 500; + margin-left: 15px; + transition: color 0.3s; + + &:hover { + color: darken($primary-color, 10%); + } + } + + .dropdown-item { + color: $text-color; + + &:hover { + background-color: $primary-color; + color: white; + } + } +} + +// Tagline styles +.tagline { + text-align: center; + color: $secondary-color; + margin-bottom: 30px; +} + +// Authentication container styles +.auth-container { + margin-top: 50px; +} + +// Card styles +.card { + border-radius: 8px; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); +} + +// Form styles +.form-label { + font-weight: bold; +} + +input[type="text"], +input[type="password"] { + border-radius: 4px; +} + +// Button styles +.btn { + transition: background-color 0.3s ease; +} + +.btn-primary { + background-color: $primary-color; + border: none; +} + +.btn-primary:hover { + background-color: darken($primary-color, 10%); +} + +.btn-outline-secondary { + color: $secondary-color; + border: 2px solid $secondary-color; + + &:hover { + background-color: $secondary-color; + color: white; + } +} + + +// Footer links styles +.footer-links { + text-align: center; + margin-top: 20px; + padding-bottom: 20px; + + a { + color: $secondary-color; + text-decoration: none; + margin: 0 10px; + + &:hover { + text-decoration: underline; + } + } +} \ No newline at end of file