initial commit of source code
commit
1f80e8b35f
@ -0,0 +1,23 @@
|
|||||||
|
/* Navigation bar */
|
||||||
|
|
||||||
|
nav {
|
||||||
|
display: flex;
|
||||||
|
background-color: #ff6600;
|
||||||
|
align-items: center;
|
||||||
|
padding: 5px 20px;
|
||||||
|
border-radius: 3px 3px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
margin: 0 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-right {
|
||||||
|
margin-left: auto;
|
||||||
|
text-align: right;
|
||||||
|
}
|
@ -0,0 +1,123 @@
|
|||||||
|
/* General typography */
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Arimo, sans-serif;
|
||||||
|
margin: 8px 7.5vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: lighter;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Site layout */
|
||||||
|
|
||||||
|
/* This is the basic box that the main part of the page goes into */
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-self: center;
|
||||||
|
background-color: #f6f6ef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Forms */
|
||||||
|
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 8px 18px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
form > * {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
form label {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 700;
|
||||||
|
display: inline-block;
|
||||||
|
width: 3.5rem;
|
||||||
|
text-align: right;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
form input {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 8px;
|
||||||
|
width: 300px;
|
||||||
|
box-shadow: 0 0 3px 1px lightgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
form input:focus {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 4px 1px darkgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
form > button {
|
||||||
|
width: 4rem;
|
||||||
|
margin: 5px 0 15px 65px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 8px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
background-color: lightslategray;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
form > button:hover {
|
||||||
|
background-color: dimgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
form > hr {
|
||||||
|
margin: 0;
|
||||||
|
border: 1px solid lightgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-input label {
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* responsive queries for tightening things up for mobile. */
|
||||||
|
|
||||||
|
@media screen and (max-width: 576px) {
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 992px) {
|
||||||
|
body {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 8px auto;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/* Lists of stories */
|
||||||
|
|
||||||
|
.stories-list {
|
||||||
|
margin: 20px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stories-list > li {
|
||||||
|
color: gray;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#stories-loading-msg {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 150%;
|
||||||
|
margin: 20px 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Individual stories */
|
||||||
|
|
||||||
|
.story-link {
|
||||||
|
color: black;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: normal;
|
||||||
|
margin: 18px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.story-link:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.story-author {
|
||||||
|
margin-left: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.story-user {
|
||||||
|
display: block;
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
/* Login and signup forms */
|
||||||
|
|
||||||
|
.account-form button {
|
||||||
|
width: 4rem;
|
||||||
|
margin-left: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#signup-form button {
|
||||||
|
width: 8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-forms-container {
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
@ -0,0 +1,106 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<!-- Hack or Snooze
|
||||||
|
|
||||||
|
This is the only HTML page; the applications manipulates this DOM
|
||||||
|
during use.
|
||||||
|
|
||||||
|
Primary authors:
|
||||||
|
- Michael Hueter: initial creation, 2018
|
||||||
|
- Elie Schoppik: refactoring using OO, 2019
|
||||||
|
- Joel Burton: refactored and componentized, 2020
|
||||||
|
- You!
|
||||||
|
-->
|
||||||
|
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>Hack or Snooze</title>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://use.fontawesome.com/releases/v5.3.1/css/all.css"
|
||||||
|
integrity="sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU"
|
||||||
|
crossorigin="anonymous">
|
||||||
|
<link rel="stylesheet" href="css/site.css">
|
||||||
|
<link rel="stylesheet" href="css/user.css">
|
||||||
|
<link rel="stylesheet" href="css/stories.css">
|
||||||
|
<link rel="stylesheet" href="css/nav.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- top navigation bar -->
|
||||||
|
<nav>
|
||||||
|
<div class="navbar-brand">
|
||||||
|
<a class="nav-link" href="#" id="nav-all">Hack or Snooze</a>
|
||||||
|
</div>
|
||||||
|
<div class="nav-right">
|
||||||
|
<a class="nav-link" href="#" id="nav-login">login/signup</a>
|
||||||
|
<a class="nav-link" href="#" id="nav-user-profile"></a>
|
||||||
|
<a class="hidden" id="nav-logout" href="#"><small>(logout)</small></a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- area for stories (all stories, user stories, favorites) -->
|
||||||
|
<section class="stories-container container">
|
||||||
|
|
||||||
|
<!-- loading message (removed by JS after stories loaded) -->
|
||||||
|
<div id="stories-loading-msg">Loading…</div>
|
||||||
|
|
||||||
|
<!-- List of all stories -->
|
||||||
|
<ol id="all-stories-list" class="stories-list"></ol>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Login and signup forms -->
|
||||||
|
<section class="account-forms-container container">
|
||||||
|
|
||||||
|
<!-- Login form -->
|
||||||
|
<form id="login-form" class="account-form hidden">
|
||||||
|
<h4>Login</h4>
|
||||||
|
<div class="login-input">
|
||||||
|
<label for="login-username">username</label>
|
||||||
|
<input id="login-username" autocomplete="current-username">
|
||||||
|
</div>
|
||||||
|
<div class="login-input">
|
||||||
|
<label for="login-password">password</label>
|
||||||
|
<input id="login-password" type="password" autocomplete="current-password">
|
||||||
|
</div>
|
||||||
|
<button type="submit">login</button>
|
||||||
|
<hr>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Signup form -->
|
||||||
|
<form id="signup-form" class="account-form hidden">
|
||||||
|
<h4>Create Account</h4>
|
||||||
|
<div class="login-input">
|
||||||
|
<label for="signup-name">name</label>
|
||||||
|
<input id="signup-name" autocapitalize="words">
|
||||||
|
</div>
|
||||||
|
<div class="login-input">
|
||||||
|
<label for="signup-username">username</label>
|
||||||
|
<input id="signup-username" autocomplete="new-username">
|
||||||
|
</div>
|
||||||
|
<div class="login-input">
|
||||||
|
<label for="signup-password">password</label>
|
||||||
|
<input id="signup-password" autocomplete="new-password" type="password">
|
||||||
|
</div>
|
||||||
|
<button type="submit">create account</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Library JS & our JS -->
|
||||||
|
<script src="https://unpkg.com/jquery"></script>
|
||||||
|
<script src="https://unpkg.com/axios/dist/axios.js"></script>
|
||||||
|
|
||||||
|
<script src="js/models.js"></script>
|
||||||
|
<script src="js/main.js"></script>
|
||||||
|
<script src="js/nav.js"></script>
|
||||||
|
<script src="js/user.js"></script>
|
||||||
|
<script src="js/stories.js"></script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,50 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
// So we don't have to keep re-finding things on page, find DOM elements once:
|
||||||
|
|
||||||
|
const $body = $("body");
|
||||||
|
|
||||||
|
const $storiesLoadingMsg = $("#stories-loading-msg");
|
||||||
|
const $allStoriesList = $("#all-stories-list");
|
||||||
|
|
||||||
|
const $loginForm = $("#login-form");
|
||||||
|
const $signupForm = $("#signup-form");
|
||||||
|
|
||||||
|
const $navLogin = $("#nav-login");
|
||||||
|
const $navUserProfile = $("#nav-user-profile");
|
||||||
|
const $navLogOut = $("#nav-logout");
|
||||||
|
|
||||||
|
/** To make it easier for individual components to show just themselves, this
|
||||||
|
* is a useful function that hides pretty much everything on the page. After
|
||||||
|
* calling this, individual components can re-show just what they want.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function hidePageComponents() {
|
||||||
|
const components = [
|
||||||
|
$allStoriesList,
|
||||||
|
$loginForm,
|
||||||
|
$signupForm,
|
||||||
|
];
|
||||||
|
components.forEach(c => c.hide());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Overall function to kick off the app. */
|
||||||
|
|
||||||
|
async function start() {
|
||||||
|
console.debug("start");
|
||||||
|
|
||||||
|
// "Remember logged-in user" and log in, if credentials in localStorage
|
||||||
|
await checkForRememberedUser();
|
||||||
|
await getAndShowStoriesOnStart();
|
||||||
|
|
||||||
|
// if we got a logged-in user
|
||||||
|
if (currentUser) updateUIOnUserLogin();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once the DOM is entirely loaded, begin the app
|
||||||
|
|
||||||
|
console.warn("HEY STUDENT: This program sends many debug messages to" +
|
||||||
|
" the console. If you don't see the message 'start' below this, you're not" +
|
||||||
|
" seeing those helpful debug messages. In your browser console, click on" +
|
||||||
|
" menu 'Default Levels' and add Verbose");
|
||||||
|
$(start);
|
@ -0,0 +1,196 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const BASE_URL = "https://hack-or-snooze-v3.herokuapp.com";
|
||||||
|
|
||||||
|
/******************************************************************************
|
||||||
|
* Story: a single story in the system
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Story {
|
||||||
|
|
||||||
|
/** Make instance of Story from data object about story:
|
||||||
|
* - {title, author, url, username, storyId, createdAt}
|
||||||
|
*/
|
||||||
|
|
||||||
|
constructor({ storyId, title, author, url, username, createdAt }) {
|
||||||
|
this.storyId = storyId;
|
||||||
|
this.title = title;
|
||||||
|
this.author = author;
|
||||||
|
this.url = url;
|
||||||
|
this.username = username;
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parses hostname out of URL and returns it. */
|
||||||
|
|
||||||
|
getHostName() {
|
||||||
|
// UNIMPLEMENTED: complete this function!
|
||||||
|
return "hostname.com";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/******************************************************************************
|
||||||
|
* List of Story instances: used by UI to show story lists in DOM.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class StoryList {
|
||||||
|
constructor(stories) {
|
||||||
|
this.stories = stories;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generate a new StoryList. It:
|
||||||
|
*
|
||||||
|
* - calls the API
|
||||||
|
* - builds an array of Story instances
|
||||||
|
* - makes a single StoryList instance out of that
|
||||||
|
* - returns the StoryList instance.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static async getStories() {
|
||||||
|
// Note presence of `static` keyword: this indicates that getStories is
|
||||||
|
// **not** an instance method. Rather, it is a method that is called on the
|
||||||
|
// class directly. Why doesn't it make sense for getStories to be an
|
||||||
|
// instance method?
|
||||||
|
|
||||||
|
// query the /stories endpoint (no auth required)
|
||||||
|
const response = await axios({
|
||||||
|
url: `${BASE_URL}/stories`,
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
|
||||||
|
// turn plain old story objects from API into instances of Story class
|
||||||
|
const stories = response.data.stories.map(story => new Story(story));
|
||||||
|
|
||||||
|
// build an instance of our own class using the new array of stories
|
||||||
|
return new StoryList(stories);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds story data to API, makes a Story instance, adds it to story list.
|
||||||
|
* - user - the current instance of User who will post the story
|
||||||
|
* - obj of {title, author, url}
|
||||||
|
*
|
||||||
|
* Returns the new Story instance
|
||||||
|
*/
|
||||||
|
|
||||||
|
async addStory( /* user, newStory */) {
|
||||||
|
// UNIMPLEMENTED: complete this function!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/******************************************************************************
|
||||||
|
* User: a user in the system (only used to represent the current user)
|
||||||
|
*/
|
||||||
|
|
||||||
|
class User {
|
||||||
|
/** Make user instance from obj of user data and a token:
|
||||||
|
* - {username, name, createdAt, favorites[], ownStories[]}
|
||||||
|
* - token
|
||||||
|
*/
|
||||||
|
|
||||||
|
constructor({
|
||||||
|
username,
|
||||||
|
name,
|
||||||
|
createdAt,
|
||||||
|
favorites = [],
|
||||||
|
ownStories = []
|
||||||
|
},
|
||||||
|
token) {
|
||||||
|
this.username = username;
|
||||||
|
this.name = name;
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
|
||||||
|
// instantiate Story instances for the user's favorites and ownStories
|
||||||
|
this.favorites = favorites.map(s => new Story(s));
|
||||||
|
this.ownStories = ownStories.map(s => new Story(s));
|
||||||
|
|
||||||
|
// store the login token on the user so it's easy to find for API calls.
|
||||||
|
this.loginToken = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Register new user in API, make User instance & return it.
|
||||||
|
*
|
||||||
|
* - username: a new username
|
||||||
|
* - password: a new password
|
||||||
|
* - name: the user's full name
|
||||||
|
*/
|
||||||
|
|
||||||
|
static async signup(username, password, name) {
|
||||||
|
const response = await axios({
|
||||||
|
url: `${BASE_URL}/signup`,
|
||||||
|
method: "POST",
|
||||||
|
data: { user: { username, password, name } },
|
||||||
|
});
|
||||||
|
|
||||||
|
const { user } = response.data;
|
||||||
|
|
||||||
|
return new User(
|
||||||
|
{
|
||||||
|
username: user.username,
|
||||||
|
name: user.name,
|
||||||
|
createdAt: user.createdAt,
|
||||||
|
favorites: user.favorites,
|
||||||
|
ownStories: user.stories
|
||||||
|
},
|
||||||
|
response.data.token
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Login in user with API, make User instance & return it.
|
||||||
|
|
||||||
|
* - username: an existing user's username
|
||||||
|
* - password: an existing user's password
|
||||||
|
*/
|
||||||
|
|
||||||
|
static async login(username, password) {
|
||||||
|
const response = await axios({
|
||||||
|
url: `${BASE_URL}/login`,
|
||||||
|
method: "POST",
|
||||||
|
data: { user: { username, password } },
|
||||||
|
});
|
||||||
|
|
||||||
|
const { user } = response.data;
|
||||||
|
|
||||||
|
return new User(
|
||||||
|
{
|
||||||
|
username: user.username,
|
||||||
|
name: user.name,
|
||||||
|
createdAt: user.createdAt,
|
||||||
|
favorites: user.favorites,
|
||||||
|
ownStories: user.stories
|
||||||
|
},
|
||||||
|
response.data.token
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** When we already have credentials (token & username) for a user,
|
||||||
|
* we can log them in automatically. This function does that.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static async loginViaStoredCredentials(token, username) {
|
||||||
|
try {
|
||||||
|
const response = await axios({
|
||||||
|
url: `${BASE_URL}/users/${username}`,
|
||||||
|
method: "GET",
|
||||||
|
params: { token },
|
||||||
|
});
|
||||||
|
|
||||||
|
const { user } = response.data;
|
||||||
|
|
||||||
|
return new User(
|
||||||
|
{
|
||||||
|
username: user.username,
|
||||||
|
name: user.name,
|
||||||
|
createdAt: user.createdAt,
|
||||||
|
favorites: user.favorites,
|
||||||
|
ownStories: user.stories
|
||||||
|
},
|
||||||
|
token
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("loginViaStoredCredentials failed", err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
/******************************************************************************
|
||||||
|
* Handling navbar clicks and updating navbar
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Show main list of all stories when click site name */
|
||||||
|
|
||||||
|
function navAllStories(evt) {
|
||||||
|
console.debug("navAllStories", evt);
|
||||||
|
evt.preventDefault();
|
||||||
|
hidePageComponents();
|
||||||
|
putStoriesOnPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
$body.on("click", "#nav-all", navAllStories);
|
||||||
|
|
||||||
|
/** Show login/signup on click on "login" */
|
||||||
|
|
||||||
|
function navLoginClick(evt) {
|
||||||
|
console.debug("navLoginClick", evt);
|
||||||
|
evt.preventDefault();
|
||||||
|
hidePageComponents();
|
||||||
|
$loginForm.show();
|
||||||
|
$signupForm.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
$navLogin.on("click", navLoginClick);
|
||||||
|
|
||||||
|
/** When a user first logins in, update the navbar to reflect that. */
|
||||||
|
|
||||||
|
function updateNavOnLogin() {
|
||||||
|
console.debug("updateNavOnLogin");
|
||||||
|
$(".main-nav-links").show();
|
||||||
|
$navLogin.hide();
|
||||||
|
$navLogOut.show();
|
||||||
|
$navUserProfile.text(`${currentUser.username}`).show();
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
// This is the global list of the stories, an instance of StoryList
|
||||||
|
let storyList;
|
||||||
|
|
||||||
|
/** Get and show stories when site first loads. */
|
||||||
|
|
||||||
|
async function getAndShowStoriesOnStart() {
|
||||||
|
storyList = await StoryList.getStories();
|
||||||
|
$storiesLoadingMsg.remove();
|
||||||
|
|
||||||
|
putStoriesOnPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A render method to render HTML for an individual Story instance
|
||||||
|
* - story: an instance of Story
|
||||||
|
*
|
||||||
|
* Returns the markup for the story.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function generateStoryMarkup(story) {
|
||||||
|
// console.debug("generateStoryMarkup", story);
|
||||||
|
|
||||||
|
const hostName = story.getHostName();
|
||||||
|
return $(`
|
||||||
|
<li id="${story.storyId}">
|
||||||
|
<a href="${story.url}" target="a_blank" class="story-link">
|
||||||
|
${story.title}
|
||||||
|
</a>
|
||||||
|
<small class="story-hostname">(${hostName})</small>
|
||||||
|
<small class="story-author">by ${story.author}</small>
|
||||||
|
<small class="story-user">posted by ${story.username}</small>
|
||||||
|
</li>
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets list of stories from server, generates their HTML, and puts on page. */
|
||||||
|
|
||||||
|
function putStoriesOnPage() {
|
||||||
|
console.debug("putStoriesOnPage");
|
||||||
|
|
||||||
|
$allStoriesList.empty();
|
||||||
|
|
||||||
|
// loop through all of our stories and generate HTML for them
|
||||||
|
for (let story of storyList.stories) {
|
||||||
|
const $story = generateStoryMarkup(story);
|
||||||
|
$allStoriesList.append($story);
|
||||||
|
}
|
||||||
|
|
||||||
|
$allStoriesList.show();
|
||||||
|
}
|
@ -0,0 +1,116 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
// global to hold the User instance of the currently-logged-in user
|
||||||
|
let currentUser;
|
||||||
|
|
||||||
|
/******************************************************************************
|
||||||
|
* User login/signup/login
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Handle login form submission. If login ok, sets up the user instance */
|
||||||
|
|
||||||
|
async function login(evt) {
|
||||||
|
console.debug("login", evt);
|
||||||
|
evt.preventDefault();
|
||||||
|
|
||||||
|
// grab the username and password
|
||||||
|
const username = $("#login-username").val();
|
||||||
|
const password = $("#login-password").val();
|
||||||
|
|
||||||
|
// User.login retrieves user info from API and returns User instance
|
||||||
|
// which we'll make the globally-available, logged-in user.
|
||||||
|
currentUser = await User.login(username, password);
|
||||||
|
|
||||||
|
$loginForm.trigger("reset");
|
||||||
|
|
||||||
|
saveUserCredentialsInLocalStorage();
|
||||||
|
updateUIOnUserLogin();
|
||||||
|
}
|
||||||
|
|
||||||
|
$loginForm.on("submit", login);
|
||||||
|
|
||||||
|
/** Handle signup form submission. */
|
||||||
|
|
||||||
|
async function signup(evt) {
|
||||||
|
console.debug("signup", evt);
|
||||||
|
evt.preventDefault();
|
||||||
|
|
||||||
|
const name = $("#signup-name").val();
|
||||||
|
const username = $("#signup-username").val();
|
||||||
|
const password = $("#signup-password").val();
|
||||||
|
|
||||||
|
// User.signup retrieves user info from API and returns User instance
|
||||||
|
// which we'll make the globally-available, logged-in user.
|
||||||
|
currentUser = await User.signup(username, password, name);
|
||||||
|
|
||||||
|
saveUserCredentialsInLocalStorage();
|
||||||
|
updateUIOnUserLogin();
|
||||||
|
|
||||||
|
$signupForm.trigger("reset");
|
||||||
|
}
|
||||||
|
|
||||||
|
$signupForm.on("submit", signup);
|
||||||
|
|
||||||
|
/** Handle click of logout button
|
||||||
|
*
|
||||||
|
* Remove their credentials from localStorage and refresh page
|
||||||
|
*/
|
||||||
|
|
||||||
|
function logout(evt) {
|
||||||
|
console.debug("logout", evt);
|
||||||
|
localStorage.clear();
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
$navLogOut.on("click", logout);
|
||||||
|
|
||||||
|
/******************************************************************************
|
||||||
|
* Storing/recalling previously-logged-in-user with localStorage
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** If there are user credentials in local storage, use those to log in
|
||||||
|
* that user. This is meant to be called on page load, just once.
|
||||||
|
*/
|
||||||
|
|
||||||
|
async function checkForRememberedUser() {
|
||||||
|
console.debug("checkForRememberedUser");
|
||||||
|
const token = localStorage.getItem("token");
|
||||||
|
const username = localStorage.getItem("username");
|
||||||
|
if (!token || !username) return false;
|
||||||
|
|
||||||
|
// try to log in with these credentials (will be null if login failed)
|
||||||
|
currentUser = await User.loginViaStoredCredentials(token, username);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sync current user information to localStorage.
|
||||||
|
*
|
||||||
|
* We store the username/token in localStorage so when the page is refreshed
|
||||||
|
* (or the user revisits the site later), they will still be logged in.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function saveUserCredentialsInLocalStorage() {
|
||||||
|
console.debug("saveUserCredentialsInLocalStorage");
|
||||||
|
if (currentUser) {
|
||||||
|
localStorage.setItem("token", currentUser.loginToken);
|
||||||
|
localStorage.setItem("username", currentUser.username);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************************************************************
|
||||||
|
* General UI stuff about users
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** When a user signs up or registers, we want to set up the UI for them:
|
||||||
|
*
|
||||||
|
* - show the stories list
|
||||||
|
* - update nav bar options for logged-in user
|
||||||
|
* - generate the user profile part of the page
|
||||||
|
*/
|
||||||
|
|
||||||
|
function updateUIOnUserLogin() {
|
||||||
|
console.debug("updateUIOnUserLogin");
|
||||||
|
|
||||||
|
$allStoriesList.show();
|
||||||
|
|
||||||
|
updateNavOnLogin();
|
||||||
|
}
|
Loading…
Reference in New Issue