Revamped UI...ish

This commit is contained in:
Gnarwhal 2021-02-05 20:19:11 -05:00
parent 13736d0ef9
commit 40a0e4046a
Signed by: Gnarwhal
GPG key ID: 0989A73D8C421174
26 changed files with 2053 additions and 621 deletions

View file

@ -1,7 +1,6 @@
package achievements; package achievements;
import achievements.misc.Password; import achievements.misc.DbConnectionService;
import achievements.services.DbConnectionService;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;

View file

@ -1,7 +1,6 @@
package achievements.controllers; package achievements.controllers;
import achievements.data.Achievements; import achievements.data.Achievements;
import achievements.data.User;
import achievements.data.Games; import achievements.data.Games;
import achievements.data.InternalError; import achievements.data.InternalError;
import achievements.services.DbService; import achievements.services.DbService;
@ -13,7 +12,6 @@ import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import static org.springframework.web.bind.annotation.RequestMethod.GET; import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
@RestController @RestController
public class Controller { public class Controller {
@ -64,47 +62,4 @@ public class Controller {
return new ResponseEntity("{}", HttpStatus.INTERNAL_SERVER_ERROR); return new ResponseEntity("{}", HttpStatus.INTERNAL_SERVER_ERROR);
} }
} }
/**
* Acceptable codes
* 0 => Success
* 1 => Email already registered
*
* -1 => Unknown error
*/
@RequestMapping(value = "/create_user", method = POST, consumes = "application/json", produces = "application/json")
public ResponseEntity createUser(@RequestBody User user) {
var status = db.createUser(user);
if (status == 0) {
return ResponseEntity.ok("{ \"key\": \"aoeuhtns\" }");
//var sessionKey = db.generateSessionKey(user);
} else {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("{ \"code\": " + status + " }");
}
}
/**
* DO NOT RETURN CODE DIRECTLY!
*
* User should only ever recieve -1, 0, or 1. The specific authentication error should be hidden.
*
* Acceptable codes
* 0 => Success
* 1 => Unregistered email address
* 2 => Incorrect password
*
* -1 => Unknown error
*/
@RequestMapping(value = "/login", method = POST, consumes = "application/json", produces = "application/json")
public ResponseEntity login(@RequestBody User user) {
var status = db.login(user);
if (status == 0) {
return ResponseEntity.ok("{ \"key\": \"aoeuhtns\" }");
} else if (status > 0) {
// Hardcoded 1 response code
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("{ \"code\": 1 }");
} else {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("{ \"code\": " + status + " }");
}
}
} }

View file

@ -0,0 +1,66 @@
package achievements.controllers;
import achievements.data.User;
import achievements.services.AuthenticationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
@RestController
public class LoginController {
@Autowired
private AuthenticationService authService;
/**
* Acceptable codes
* 0 => Success
* 1 => Email already registered
*
* -1 => Unknown error
*/
@RequestMapping(value = "/create_user", method = POST, consumes = "application/json", produces = "application/json")
public ResponseEntity createUser(@RequestBody User user) {
var response = authService.createUser(user);
if (response.status == 0) {
return ResponseEntity.ok("{ \"key\": \"" + authService.session().generate(response.id) + "\", \"id\": " + response.id + " }");
} else if (response.status > 0) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("{ \"code\": " + response.status + " }");
} else {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("{ \"code\": " + response.status + " }");
}
}
/**
* DO NOT RETURN CODE DIRECTLY!
*
* User should only ever recieve -1, 0, or 1. The specific authentication error should be hidden.
*
* Acceptable codes
* 0 => Success
* 1 => Unregistered email address
* 2 => Incorrect password
*
* -1 => Unknown error
*/
@RequestMapping(value = "/login", method = POST, consumes = "application/json", produces = "application/json")
public ResponseEntity login(@RequestParam(value = "guest", required = false) boolean guest, @RequestBody User user) {
var response = guest ?
authService.GUEST :
authService.login(user);
if (response.status == 0) {
return ResponseEntity.ok("{ \"key\": \"" + authService.session().generate(response.id) + "\", \"id\": " + response.id + " }");
} else if (response.status > 0) {
// Hardcoded 1 response code
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("{ \"code\": 1 }");
} else {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("{ \"code\": " + response.status + " }");
}
}
}

View file

@ -1,56 +1,56 @@
package achievements.services; package achievements.misc;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import com.microsoft.sqlserver.jdbc.SQLServerDataSource; import com.microsoft.sqlserver.jdbc.SQLServerDataSource;
@Component @Component
public class DbConnectionService { public class DbConnectionService {
private Connection connection; private Connection connection;
@Value("${database.server}") @Value("${database.server}")
private String serverName; private String serverName;
@Value("${database.name}") @Value("${database.name}")
private String databaseName; private String databaseName;
@Value("${database.user.name}") @Value("${database.user.name}")
private String username; private String username;
@Value("${database.user.password}") @Value("${database.user.password}")
private String password; private String password;
public DbConnectionService() {} public DbConnectionService() {}
@PostConstruct @PostConstruct
public void connect() { public void connect() {
try { try {
var dataSource = new SQLServerDataSource(); var dataSource = new SQLServerDataSource();
dataSource.setServerName (serverName ); dataSource.setServerName (serverName );
dataSource.setDatabaseName(databaseName); dataSource.setDatabaseName(databaseName);
dataSource.setUser (username ); dataSource.setUser (username );
dataSource.setPassword (password ); dataSource.setPassword (password );
connection = dataSource.getConnection(); connection = dataSource.getConnection();
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
public Connection getConnection() { public Connection getConnection() {
return this.connection; return this.connection;
} }
@PreDestroy @PreDestroy
public void disconnect() { public void disconnect() {
try { try {
if (connection != null) { if (connection != null) {
connection.close(); connection.close();
} }
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
} }

View file

@ -0,0 +1,70 @@
package achievements.misc;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Random;
public class HashManager {
private static final Random RANDOM = new SecureRandom();
public static byte[] hash(byte[] salt, byte[] password) {
try {
var concat = new byte[salt.length + password.length];
int i = 0;
for (; i < salt.length; ++i) {
concat[i] = salt[i];
}
for (int j = 0; j < password.length; ++j) {
concat[i + j] = password[j];
}
var md = MessageDigest.getInstance("SHA-256");
return md.digest(concat);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
public static String encode(byte[] bytes) {
var chars = new char[bytes.length << 1];
for (int i = 0; i < bytes.length; ++i) {
chars[(i << 1) ] = toHex(bytes[i] >> 0);
chars[(i << 1) + 1] = toHex(bytes[i] >> 4);
}
return new String(chars);
}
public static byte[] decode(String data) {
var decoded = new byte[data.length() >> 1];
for (int i = 0; i < data.length(); i += 2) {
int currentByte =
(fromHex(data.charAt(i )) ) |
(fromHex(data.charAt(i + 1)) << 4);
decoded[i >> 1] = (byte) (currentByte & 0xFF);
}
return decoded;
}
public static byte[] generateBytes(int length) {
var bytes = new byte[length];
RANDOM.nextBytes(bytes);
return bytes;
}
public static char toHex(int halfByte) {
halfByte = halfByte & 0xF;
if (0 <= halfByte && halfByte <= 9 ) return (char) (halfByte + '0' );
if (10 <= halfByte && halfByte <= 15) return (char) (halfByte + 'a' - 10);
return '0';
}
public static int fromHex(char c) {
if ('0' <= c && c <= '9') return c - '0';
if ('A' <= c && c <= 'F') return c - 'A' + 10;
if ('a' <= c && c <= 'f') return c - 'a' + 10;
return 0;
}
}

View file

@ -1,14 +1,7 @@
package achievements.misc; package achievements.misc;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Random;
public class Password { public class Password {
private static final Random RANDOM = new SecureRandom();
public final String salt; public final String salt;
public final String hash; public final String hash;
@ -19,19 +12,17 @@ public class Password {
public static Password generate(String password) { public static Password generate(String password) {
// Generate the salt // Generate the salt
var salt = new byte[16]; // 128 bits var salt = HashManager.generateBytes(16); // 128 bits
RANDOM.nextBytes(salt);
return new Password( return new Password(
encode(salt), HashManager.encode(salt),
encode(hash(salt, password.getBytes())) HashManager.encode(HashManager.hash(salt, password.getBytes()))
); );
} }
public static boolean validate(String salt, String password, String hash) { public static boolean validate(String salt, String password, String hash) {
System.out.println(salt + ", " + password); var srcHash = HashManager.hash(HashManager.decode(salt), password.getBytes());
var srcHash = hash(decode(salt), password.getBytes()); var targetHash = HashManager.decode(hash);
var targetHash = decode(hash);
for (int i = 0; i < srcHash.length; ++i) { for (int i = 0; i < srcHash.length; ++i) {
if (srcHash[i] != targetHash[i]) { if (srcHash[i] != targetHash[i]) {
return false; return false;
@ -39,57 +30,4 @@ public class Password {
} }
return true; return true;
} }
private static byte[] hash(byte[] salt, byte[] password) {
try {
var concat = new byte[salt.length + password.length];
int i = 0;
for (; i < salt.length; ++i) {
concat[i] = salt[i];
}
for (int j = 0; j < password.length; ++j) {
concat[i + j] = password[j];
}
var md = MessageDigest.getInstance("SHA-256");
return md.digest(concat);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
private static String encode(byte[] bytes) {
var chars = new char[bytes.length << 1];
for (int i = 0; i < bytes.length; ++i) {
chars[(i << 1) ] = toHex(bytes[i] >> 0);
chars[(i << 1) + 1] = toHex(bytes[i] >> 4);
}
return new String(chars);
}
private static byte[] decode(String data) {
var decoded = new byte[data.length() >> 1];
for (int i = 0; i < data.length(); i += 2) {
int currentByte =
(fromHex(data.charAt(i )) ) |
(fromHex(data.charAt(i + 1)) << 4);
decoded[i >> 1] = (byte) (currentByte & 0xFF);
}
return decoded;
}
private static char toHex(int halfByte) {
halfByte = halfByte & 0xF;
if (0 <= halfByte && halfByte <= 9 ) return (char) (halfByte + '0' );
if (10 <= halfByte && halfByte <= 15) return (char) (halfByte + 'a' - 10);
return '0';
}
private static int fromHex(char c) {
if ('0' <= c && c <= '9') return c - '0';
if ('A' <= c && c <= 'F') return c - 'A' + 10;
if ('a' <= c && c <= 'f') return c - 'a' + 10;
return 0;
}
} }

View file

@ -0,0 +1,28 @@
package achievements.misc;
import java.util.HashMap;
public class SessionManager {
private HashMap<String, Integer> session;
public SessionManager() {
session = new HashMap();
}
public String generate(Integer user) {
var key = HashManager.encode(HashManager.generateBytes(16));
session.put(key, user);
return key;
}
public String guest() {
var key = HashManager.encode(HashManager.generateBytes(16));
session.put(key, null);
return key;
}
public Integer getUser(String key) {
return session.get(key);
}
}

View file

@ -0,0 +1,106 @@
package achievements.services;
import achievements.data.User;
import achievements.misc.DbConnectionService;
import achievements.misc.Password;
import achievements.misc.SessionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.sql.*;
@Service
public class AuthenticationService {
public static class LoginResponse {
public int status;
public Integer id;
public LoginResponse() {
this.status = 0;
this.id = null;
}
public LoginResponse(int status) {
this.status = status;
this.id = null;
}
public LoginResponse(int status, int id) {
this.status = status;
this.id = id;
}
}
public static final LoginResponse GUEST = new LoginResponse();
@Autowired
private DbConnectionService dbs;
private Connection db;
private SessionManager session;
@PostConstruct
private void init() {
db = dbs.getConnection();
session = new SessionManager();
}
public LoginResponse createUser(User user) {
if (!user.getEmail().matches(".+@\\w+\\.\\w+")) {
return new LoginResponse(2);
}
try {
var statement = db.prepareCall("{? = call CreateUser(?, ?, ?, ?, ?)}");
statement.registerOutParameter(1, Types.INTEGER);
statement.setString(2, user.getEmail());
statement.setString(3, user.getUsername());
var password = Password.generate(user.getPassword());
statement.setString(4, password.salt);
statement.setString(5, password.hash);
statement.registerOutParameter(6, Types.INTEGER);
statement.execute();
var response = new LoginResponse(statement.getInt(1), statement.getInt(6));
statement.close();
return response;
} catch (SQLException e) {
e.printStackTrace();
}
return new LoginResponse(-1);
}
public LoginResponse login(User user) {
var response = new LoginResponse(-1);
try {
var statement = db.prepareCall("{call GetUserLogin(?)}");
statement.setString(1, user.email);
var result = statement.executeQuery();
if (result.next()) {
var salt = result.getString("Salt");
var hash = result.getString("Password");
if (Password.validate(salt, user.getPassword(), hash)) {
response = new LoginResponse(0, result.getInt("ID"));
} else {
response = new LoginResponse(2);
}
} else {
response = new LoginResponse(1);
}
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
return response;
}
public SessionManager session() {
return session;
}
}

View file

@ -1,134 +1,92 @@
package achievements.services; package achievements.services;
import achievements.data.Achievements; import achievements.data.Achievements;
import achievements.data.Games; import achievements.data.Games;
import achievements.data.User; import achievements.misc.DbConnectionService;
import achievements.misc.Password; import achievements.misc.SessionManager;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.sql.*; import java.sql.*;
@Service @Service
public class DbService { public class DbService {
@Autowired @Autowired
private DbConnectionService dbs; private DbConnectionService dbs;
private Connection db; private Connection db;
@PostConstruct @PostConstruct
private void init() { db = dbs.getConnection(); } private void init() {
db = dbs.getConnection();
public Achievements getAchievements(String gameName) { }
try {
// Create Query public Achievements getAchievements(String gameName) {
CallableStatement stmt = db.prepareCall("{? = call GetAchievements(?)}"); try {
stmt.registerOutParameter(1, Types.INTEGER); // Create Query
stmt.setString(2, gameName); CallableStatement stmt = db.prepareCall("{? = call GetAchievements(?)}");
stmt.registerOutParameter(1, Types.INTEGER);
// Read Result(s) stmt.setString(2, gameName);
ResultSet results = stmt.executeQuery();
var achievements = new Achievements(); // Read Result(s)
while (results.next()) { ResultSet results = stmt.executeQuery();
// Add Result(s) to data class var achievements = new Achievements();
int achievementGameID = results.getInt("GameID"); while (results.next()) {
String achievementGameName = results.getString("GameName"); // Add Result(s) to data class
String achievementName = results.getString("Name"); int achievementGameID = results.getInt("GameID");
String achievementDescription = results.getString("Description"); String achievementGameName = results.getString("GameName");
int achievementStages = results.getInt("Stages"); String achievementName = results.getString("Name");
// Checks if getting from specific game or all achievements String achievementDescription = results.getString("Description");
if (!gameName.equals("%")) { int achievementStages = results.getInt("Stages");
achievements.setGameID(achievementGameID); // Checks if getting from specific game or all achievements
achievements.setGameName(achievementGameName); if (!gameName.equals("%")) {
} achievements.setGameID(achievementGameID);
achievements.addAchievement(new Achievements.Achievement(achievementName, achievementDescription, achievementStages)); achievements.setGameName(achievementGameName);
} }
stmt.close(); achievements.addAchievement(new Achievements.Achievement(achievementName, achievementDescription, achievementStages));
return achievements; }
} catch (SQLException e) { stmt.close();
e.printStackTrace(); return achievements;
return null; } catch (SQLException e) {
} e.printStackTrace();
} return null;
}
public Games getGames(String name) { }
try {
// Create Query public Games getGames(String name) {
CallableStatement stmt = db.prepareCall("{? = call GetGame(?)}"); try {
stmt.registerOutParameter(1, Types.INTEGER); // Create Query
stmt.setString(2, name); CallableStatement stmt = db.prepareCall("{? = call GetGame(?)}");
stmt.registerOutParameter(1, Types.INTEGER);
// Read Result(s) stmt.setString(2, name);
ResultSet results = stmt.executeQuery();
var games = new Games(); // Read Result(s)
while (results.next()) { ResultSet results = stmt.executeQuery();
// Add Result(s) to data class var games = new Games();
int gameID = results.getInt("ID"); while (results.next()) {
String gameName = results.getString("Name"); // Add Result(s) to data class
String gamePlatform = results.getString("PlatformName"); int gameID = results.getInt("ID");
if (!games.getGames().isEmpty()) { String gameName = results.getString("Name");
var lastGame = games.getGames().get(games.getGames().size()-1); String gamePlatform = results.getString("PlatformName");
if (lastGame.getId() == gameID) { if (!games.getGames().isEmpty()) {
lastGame.addToPlatforms(gamePlatform); var lastGame = games.getGames().get(games.getGames().size()-1);
} else { if (lastGame.getId() == gameID) {
games.addGame(new Games.Game(gameID,gameName,gamePlatform)); lastGame.addToPlatforms(gamePlatform);
} } else {
} else { games.addGame(new Games.Game(gameID,gameName,gamePlatform));
games.addGame(new Games.Game(gameID,gameName,gamePlatform)); }
} } else {
games.addGame(new Games.Game(gameID,gameName,gamePlatform));
} }
stmt.close();
return games; }
} catch (SQLException e) { stmt.close();
e.printStackTrace(); return games;
return null; } catch (SQLException e) {
} e.printStackTrace();
} return null;
}
public int createUser(User user) { }
try {
var statement = db.prepareCall("{? = call CreateUser(?, ?, ?, ?)}"); }
statement.registerOutParameter(1, Types.INTEGER);
statement.setString(2, user.getEmail());
statement.setString(3, user.getUsername());
var password = Password.generate(user.getPassword());
statement.setString(4, password.salt);
statement.setString(5, password.hash);
statement.execute();
var code = statement.getInt(1);
statement.close();
return code;
} catch (SQLException e) {
e.printStackTrace();
}
return -1;
}
public int login(User user) {
try {
var statement = db.prepareStatement("SELECT Salt, Password FROM [dbo].[User] WHERE Email = ?");
statement.setString(1, user.email);
var result = statement.executeQuery();
if (result.next()) {
var salt = result.getString("Salt");
var hash = result.getString("Password");
if (Password.validate(salt, user.getPassword(), hash)) {
return 0;
} else {
return 2;
}
} else {
return 1;
}
} catch (SQLException e) {
e.printStackTrace();
}
return -1;
}
}

View file

@ -23,9 +23,11 @@
<div id="content-body"> <div id="content-body">
<template data-template="content-body: List<Basic>"> <template data-template="content-body: List<Basic>">
<div id="${page}-page" class="page"> <div id="${page}-page" class="page">
<div class="page-header"> <div class="page-subsection">
<p class="page-header-text">${title}</p> <div class="page-header">
<div class="page-header-separator"></div> <p class="page-header-text">${title}</p>
<div class="page-header-separator"></div>
</div>
</div> </div>
<template data-template="extern-${page}-page: Extern"></template> <template data-template="extern-${page}-page: Extern"></template>
</div> </div>

View file

@ -21,16 +21,17 @@
<div class="page-subheader-separator"></div> <div class="page-subheader-separator"></div>
</div> </div>
<div id="login-form"> <div id="login-form">
<p id="error-message">Egg</p> <p id="error-message" class="form-row top multiline">Egg</p>
<input id="email" class="login-field" type="text" placeholder="Email"></input> <input id="email" class="login-field form-row" type="text" placeholder="Email"></input>
<input id="username" class="login-field" type="text" placeholder="Username"></input> <input id="username" class="login-field form-row" type="text" placeholder="Username"></input>
<input id="password" class="login-field" type="password" placeholder="Password"></input> <input id="password" class="login-field form-row" type="password" placeholder="Password"></input>
<input id="confirm" class="login-field" type="password" placeholder="Confirm your password"></input> <input id="confirm" class="login-field form-row" type="password" placeholder="Confirm your password"></input>
<div id="login-buttons"> <div id="button-row" class="form-row">
<div id="create-user-button" class="ap-button login">Create Account &gt;</div> <div id="create-user-button" class="ap-button login">Create Account</div>
<div id="login-button" class="ap-button login">Login</div> <div id="guest-login-button" class="ap-button login">Continue as Guest</div>
</div> </div>
<p id="warning">WARNING! The security of this project is questionable at best. Please refrain from using any truly sensitive data.</p> <div id="login-button" class="ap-button login form-row">Login</div>
<p id="warning" class="form-row multiline">WARNING! The security of this project is questionable at best. Please refrain from using any truly sensitive data.</p>
</div> </div>
</div> </div>
</div> </div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

View file

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

View file

@ -8,6 +8,7 @@ window.addEventListener("load", (loadEvent) => {
const createUser = document.querySelector("#create-user-button"); const createUser = document.querySelector("#create-user-button");
const login = document.querySelector("#login-button"); const login = document.querySelector("#login-button");
const guest = document.querySelector("#guest-login-button");
const header = document.querySelector("#login-header-text"); const header = document.querySelector("#login-header-text");
const error = document.querySelector("#error-message"); const error = document.querySelector("#error-message");
@ -31,11 +32,7 @@ window.addEventListener("load", (loadEvent) => {
if (!frozen) { if (!frozen) {
fields.username.style.display = "block"; fields.username.style.display = "block";
fields.confirm.style.display = "block"; fields.confirm.style.display = "block";
header.textContent = "Create User"; header.textContent = "Create Account";
login.style.flexGrow = "0";
login.textContent = "< Login";
createUser.style.flexGrow = "1";
createUser.textContent = "Create User";
createUser.removeEventListener("click", switchToCreateAction); createUser.removeEventListener("click", switchToCreateAction);
createUser.addEventListener("click", createUserAction); createUser.addEventListener("click", createUserAction);
@ -65,10 +62,11 @@ window.addEventListener("load", (loadEvent) => {
body: JSON.stringify({ email: fields.email.value, username: fields.username.value, password: fields.password.value }) body: JSON.stringify({ email: fields.email.value, username: fields.username.value, password: fields.password.value })
}) })
.then(async response => ({ status: response.status, data: await response.json() })) .then(async response => ({ status: response.status, data: await response.json() }))
.then(response => { .then(response =>{
const data = response.data; const data = response.data;
if (response.status === 200) { if (response.status === 200) {
window.sessionStorage.setItem('sessionKey', data.key); window.sessionStorage.setItem('sessionKey', data.key);
window.sessionStorage.setItem('id', data.id );
window.location.href = "/"; window.location.href = "/";
} else if (response.status === 500) { } else if (response.status === 500) {
raiseError([], "Internal server error :("); raiseError([], "Internal server error :(");
@ -76,6 +74,9 @@ window.addEventListener("load", (loadEvent) => {
if (data.code === 1) { if (data.code === 1) {
raiseError([ "email" ], "A user with that email is already registered"); raiseError([ "email" ], "A user with that email is already registered");
fields.email.value = ''; fields.email.value = '';
} else if (data.code === 2) {
raiseError([ "email" ], "Invalid email address");
fields.email.value = '';
} }
} }
}) })
@ -93,10 +94,6 @@ window.addEventListener("load", (loadEvent) => {
fields.username.style.display = "none"; fields.username.style.display = "none";
fields.confirm.style.display = "none"; fields.confirm.style.display = "none";
header.textContent = "Login"; header.textContent = "Login";
login.style.flexGrow = "1"
login.textContent = "Login";
createUser.style.flexGrow = "0";
createUser.textContent = "Create User >";
createUser.removeEventListener("click", createUserAction); createUser.removeEventListener("click", createUserAction);
createUser.addEventListener("click", switchToCreateAction); createUser.addEventListener("click", switchToCreateAction);
@ -125,7 +122,9 @@ window.addEventListener("load", (loadEvent) => {
.then(response => { .then(response => {
const data = response.data; const data = response.data;
if (response.status === 200) { if (response.status === 200) {
console.log(data);
window.sessionStorage.setItem('sessionKey', data.key); window.sessionStorage.setItem('sessionKey', data.key);
window.sessionStorage.setItem('id', data.id );
window.location.href = "/"; window.location.href = "/";
} else if (response.status === 500) { } else if (response.status === 500) {
raiseError([], "Internal server error :("); raiseError([], "Internal server error :(");
@ -142,4 +141,37 @@ window.addEventListener("load", (loadEvent) => {
} }
}; };
login.addEventListener("click", loginAction); login.addEventListener("click", loginAction);
guest.addEventListener("click", (clickEvent) => {
if (!frozen) {
frozen = true;
fetch('https://localhost:4730/login?guest=true', {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
body: "{}"
})
.then(async response => ({ status: response.status, data: await response.json() }))
.then(response => {
const data = response.data;
if (response.status === 200) {
console.log(data);
window.sessionStorage.setItem('sessionKey', data.key);
window.sessionStorage.setItem('id', data.id );
window.location.href = "/";
} else if (response.status === 500) {
raiseError([], "Internal server error :(");
} else {
raiseError([ "email", "password" ], "Email or password is incorrect");
fields.password.value = '';
}
})
.catch(error => {
console.error(error);
raiseError([], "Unknown error :(");
}).then(() => frozen = false);
}
});
}); });

View file

@ -1,3 +1,7 @@
:root {
--shadow-color: rgba(0, 0, 0, 0.5);
}
html, body { html, body {
background-color: var(--background-dark); background-color: var(--background-dark);
@ -27,7 +31,7 @@ html, body {
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
box-shadow: 0px 0px 5px 10px rgba(0, 0, 0, 0.5); box-shadow: 0px 0px 5px 10px var(--shadow-color);
} }
.navbar-section { .navbar-section {
@ -67,20 +71,17 @@ html, body {
} }
.ap-button { .ap-button {
box-sizing: border-box;
padding: 12px 16px;
color: var(--foreground); color: var(--foreground);
background-color: var(--accent-value2); background-color: var(--accent-value2);
font-size: 18px;
text-align: center;
border-radius: 4px; border-radius: 4px;
cursor: default; cursor: default;
display: flex;
justify-content: center;
align-items: center;
transition-property: background-color; transition-property: background-color;
transition-duration: 0.15s; transition-duration: 0.15s;
} }
@ -112,33 +113,61 @@ html, body {
box-sizing: border-box; box-sizing: border-box;
padding: 0px 64px; padding: 32px;
width: 100%; width: 100%;
height: max-content; height: max-content;
min-height: 100%; min-height: 100%;
background-color: var(--background); background-color: var(--background);
box-shadow: 0px 0px 5px 10px rgba(0, 0, 0, 0.5); box-shadow: 0px 0px 5px 10px var(--shadow-color);
display: none; display: none;
} }
.page-subsection {
box-sizing: border-box;
margin: 32px;
padding: 16px;
background-color: var(--background-dark);
border-radius: 8px;
box-shadow: inset 0px 0px 8px 8px var(--shadow-color);
position: relative;
z-index: -1;
}
.page-subsection-wrapper {
padding: 16px;
}
.page-subsection-chunk {
box-shadow: 0px 0px 8px 8px var(--shadow-color);
border-radius: 8px;
overflow: hidden;
}
.page-header { .page-header {
box-sizing: border-box; box-sizing: border-box;
padding: 64px 0px;
width: 100%;
height: max-content; height: max-content;
margin: 16px;
}
.page-subheader {
margin-bottom: 16px;
} }
.page-header-text, .page-header-text,
.page-subheader-text { .page-subheader-text {
width: max-content; width: max-content;
margin: 0; margin: 0 0 0.25em;
margin-bottom: 0.25em;
color: var(--foreground); color: var(--foreground);
@ -157,11 +186,11 @@ html, body {
} }
.page-header-text { .page-header-text {
font-size: 64px; font-size: 48px;
} }
.page-subheader-text { .page-subheader-text {
font-size: 48px; font-size: 32px;
} }
.page-header-separator, .page-header-separator,
@ -175,9 +204,7 @@ html, body {
.list-page-search { .list-page-search {
box-sizing: border-box; box-sizing: border-box;
padding: 0px 64px 32px; margin: 16px;
width: 100%;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -197,8 +224,6 @@ html, body {
.list-page-search > label { .list-page-search > label {
background-color: var(--accent-value2); background-color: var(--accent-value2);
border-radius: 8px 0px 0px 8px;
} }
.list-page-search > label:hover { .list-page-search > label:hover {
@ -216,7 +241,6 @@ html, body {
background-color: var(--distinction); background-color: var(--distinction);
border: 0; border: 0;
border-radius: 0px 8px 8px 0px;
flex-grow: 1; flex-grow: 1;
@ -235,8 +259,6 @@ html, body {
.list-page-partitions { .list-page-partitions {
box-sizing: border-box; box-sizing: border-box;
padding: 32px 64px;
width: 100%; width: 100%;
height: max-content; height: max-content;
@ -251,9 +273,11 @@ html, body {
max-width: 640px; max-width: 640px;
} }
.list-page-filter { .list-page-filter-chunk {
margin-top: 16px; background-color: var(--distinction);
}
.list-page-filter {
box-sizing: border-box; box-sizing: border-box;
width: 100%; width: 100%;
@ -269,9 +293,9 @@ html, body {
width: 32px; width: 32px;
height: 32px; height: 32px;
background-color: var(--background); background-color: var(--foreground);
border: 3px solid var(--distinction); border: 3px solid var(--foreground-dark);
border-radius: 8px; border-radius: 8px;
transition-property: background-color, border-color; transition-property: background-color, border-color;
@ -279,7 +303,7 @@ html, body {
} }
.list-page-filter:hover > .list-page-filter-checkbox { .list-page-filter:hover > .list-page-filter-checkbox {
background-color: var(--background); background-color: var(--foreground);
border-color: var(--selected-accent1); border-color: var(--selected-accent1);
} }
@ -307,8 +331,6 @@ html, body {
.list-page-list-partition { .list-page-list-partition {
box-sizing: border-box; box-sizing: border-box;
padding-left: 64px;
flex-grow: 1; flex-grow: 1;
} }

View file

@ -49,7 +49,7 @@ html, body {
} }
#profile-page { #profile-page {
max-width: 1920px; max-width: 1600px;
display: block; display: block;
} }
@ -67,43 +67,54 @@ html, body {
} }
#profile-info { #profile-info {
box-sizing: border-box; width: 50%;
width: max-content;
height: max-content; height: max-content;
max-width: 50%; max-width: 480px;
padding: 0px 64px;
padding-bottom: 128px;
} }
#profile-info-pfp { #profile-info-pfp-border {
box-sizing: border-box;
padding: 24px;
background-color: var(--distinction);
width: 100%; width: 100%;
max-width: 640px; max-width: 640px;
height: max-content;
margin-bottom: 1.25em; box-shadow: 0px 0px 8px 8px var(--shadow-color);
border-radius: 8px;
position: relative;
z-index: -1;
} }
#profile-info-name { #profile-info-pfp-vignette {
margin: 0; box-shadow: inset 0px 0px 50px 30px var(--shadow-color);
padding: 16px 0px; border-radius: 8px;
}
font-size: 42px; #profile-info-pfp-img {
display: block;
width: 100%;
border-radius: 8px;
color: var(--foreground); position: relative;
z-index: -1;
} }
.profile-list { .profile-list {
margin-top: 1.25em;
width: 100%; width: 100%;
height: max-content; height: max-content;
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
box-shadow: 0px 0px 8px 8px var(--shadow-color);
} }
.profile-entry { .profile-entry {
@ -117,13 +128,9 @@ html, body {
align-items: center; align-items: center;
background-color: var(--distinction); background-color: var(--distinction);
border-bottom: 1px solid var(--background);
} }
.profile-entry.accented { .profile-entry.accented {
border-bottom: 1px solid var(--accent-value0);
background-color: var(--accent-value1); background-color: var(--accent-value1);
} }
@ -147,15 +154,40 @@ html, body {
.profile-entry-icon { .profile-entry-icon {
width: 64px; width: 64px;
flex-grow: 0;
} }
.profile-entry-text { .profile-entry-text {
box-sizing: border-box;
margin: 0; margin: 0;
padding: 0px 16px; padding: 0px 16px;
height: 100%;
line-height: 64px;
color: var(--foreground); color: var(--foreground);
font-size: 24px; font-size: 24px;
border-top: 1px solid var(--background);
flex-grow: 0;
}
.top > .profile-entry-text {
border: 0;
}
.accented > .profile-entry-text {
border-color: var(--accent-value0);
}
.profile-entry-text.platform-name,
.profile-entry-text.game-name,
.profile-entry-text.achievement-name {
flex-grow: 1;
} }
.profile-entry-text.accented { .profile-entry-text.accented {
@ -167,12 +199,6 @@ html, body {
} }
#profile-platforms { #profile-platforms {
box-sizing: border-box;
height: max-content;
padding: 0px 64px;
flex-grow: 1; flex-grow: 1;
} }
@ -188,18 +214,8 @@ html, body {
align-items: flex-start; align-items: flex-start;
} }
#profile-games { #profile-games,
box-sizing: border-box; #profile-achievements {
padding: 0px 64px 64px;
width: 50%; width: 50%;
height: max-content; height: max-content;
} }
#profile-achievements {
box-sizing: border-box;
padding: 0px 64px 64px;
width: 50%;
height: max-content;
}

View file

@ -34,33 +34,36 @@
border-radius: 8px; border-radius: 8px;
} }
.form-row {
margin: var(--element-spacing) var(--form-spacing) 0;
border: 0;
padding: 0;
width: calc(100% - var(--form-spacing) * 2);
height: 40px;
}
.form-row.top {
margin-top: 0;
}
.form-row.multiline {
height: max-content;
}
#error-message { #error-message {
display: none; display: none;
box-sizing: border-box; line-height: 40px;
margin: 0 var(--form-spacing) var(--element-spacing);
width: calc(100% - (var(--form-spacing) * 2));
color: var(--error); color: var(--error);
font-size: 20px; font-size: 20px;
} }
.login-field { .login-field {
box-sizing: border-box; box-sizing: border-box;
padding: 0 var(--element-spacing);
margin: 0 var(--form-spacing) var(--element-spacing); font-size: 18px;
border: 0;
padding: 8px;
width: calc(100% - (var(--form-spacing) * 2));
height: max-content;
font-size: 20px;
border-radius: 4px; border-radius: 4px;
outline: none; outline: none;
} }
@ -68,41 +71,34 @@
background-color: var(--error); background-color: var(--error);
} }
.ap-button.login {
height: 40px;
font-size: 18px;
}
#username, #username,
#confirm { #confirm {
display: none; display: none;
} }
#login-buttons { #button-row {
margin: 0 calc(var(--form-spacing) - (var(--element-spacing) / 2));
width: calc(100% - (var(--form-spacing) * 2) + var(--element-spacing));
height: max-content;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
} }
.ap-button.login { #create-user-button,
margin: 0 calc(var(--element-spacing) / 2); #guest-login-button {
flex-basis: max-content; width: calc(50% - var(--element-spacing) / 2);
}
#create-user-button {
flex-grow: 0;
} }
#login-button { #login-button {
flex-grow: 1; width: calc(100% - var(--form-spacing * 2))
} }
#warning { #warning {
box-sizing: border-box;
padding: 0 var(--form-spacing);
color: var(--foreground); color: var(--foreground);
font-size: 24px; font-size: 24px;
} }

View file

@ -1,16 +1,20 @@
:root { :root {
--background-dark: #111115; --background-dark: #111117;
--background: #22222A; --background: #22222A;
--foreground-dark: #AAAAAA; --foreground-dark: #AAAAAA;
--foreground: #EEEEEE; --foreground: #EEEEEE;
--distinction: #44444F; --distinction: #44444C;
--accent-value0: #500000; --accent-hue: 0;
--accent-value1: #800000;
--accent-value2: #A00000;
--accent-value3: #D02020;
--accent-value4: #FA7575;
--selected-accent0: #0066CC; --accent-value0: hsl(var(--accent-hue), 100%, 16%);
--accent-value1: hsl(var(--accent-hue), 100%, 25%);
--accent-value2: hsl(var(--accent-hue), 100%, 31%);
--accent-value3: hsl(var(--accent-hue), 73%, 47%);
--accent-value4: hsl(var(--accent-hue), 83%, 57%);
--accent-value5: hsl(var(--accent-hue), 93%, 72%);
--accent-pure: hsl(var(--accent-hue), 100%, 50%);
--selected-accent0: #2266CC;
--selected-accent1: #3388FF; --selected-accent1: #3388FF;
} }

View file

@ -1,40 +1,54 @@
<div class="list-page-search"> <div class="page-subsection">
<label for="achievement-search">Search</label> <div class="list-page-search page-subsection-chunk">
<input id="achievement-search" type="text" placeholder="Name, Keyword, etc..." achievement-name="achievement-search" /> <label for="achievement-search">Search</label>
</div> <input id="achievement-search" type="text" placeholder="Name, Keyword, etc..." name="achievement-search"/>
<div class="list-page-partitions">
<div class="list-page-filter-partition">
<p class="page-subheader-text">Filters</p>
<div class="page-subheader-separator"></div>
<div id="from-games-owned-filter" class="list-page-filter">
<div class="list-page-filter-checkbox"></div>
<p class="list-page-filter-name">From Games Owned</p>
</div>
<div id="in-progress-filter" class="list-page-filter">
<div class="list-page-filter-checkbox"></div>
<p class="list-page-filter-name">In Progress</p>
</div>
<div id="completed-filter" class="list-page-filter">
<div class="list-page-filter-checkbox"></div>
<p class="list-page-filter-name">Completed</p>
</div>
</div> </div>
<div class="list-page-list-partition"> <div class="list-page-partitions">
<div class="list-page-list"> <div class="list-page-filter-partition">
<div class="list-page-header"> <div class="page-subsection-wrapper">
<p class="list-page-entry-icon"></p> <div class="page-subheader">
<p class="list-page-entry-text achievement-name">Name</p> <p class="page-subheader-text">Filters</p>
<p class="list-page-entry-text achievement-description">Description</p> <div class="page-subheader-separator"></div>
<p class="list-page-entry-text achievement-stages">Stages</p>
</div>
<template data-template="achievements-page-list: List<Basic>">
<div class="list-page-entry">
<img class="list-page-entry-icon" src="res/dummy_achievement.png" alt="Achievement Thumbnail"></img>
<p class="list-page-entry-text achievement-name">${achievement-name}</p>
<p class="list-page-entry-text achievement-description">${achievement-description}</p>
<p class="list-page-entry-text achievement-stages">${stages}</p>
</div> </div>
</template> <div class="list-page-filter-chunk page-subsection-chunk">
<div class="page-subsection-wrapper">
<div id="from-games-owned-filter" class="list-page-filter">
<div class="list-page-filter-checkbox"></div>
<p class="list-page-filter-name">From Games Owned</p>
</div>
<div id="in-progress-filter" class="list-page-filter">
<div class="list-page-filter-checkbox"></div>
<p class="list-page-filter-name">In Progress</p>
</div>
<div id="completed-filter" class="list-page-filter">
<div class="list-page-filter-checkbox"></div>
<p class="list-page-filter-name">Completed</p>
</div>
</div>
</div>
</div>
</div>
<div class="list-page-list-partition page-subsection-wrapper">
<div class="page-subsection-chunk">
<div class="list-page-list">
<div class="list-page-header">
<p class="list-page-entry-icon"></p>
<p class="list-page-entry-text achievement-name">Name</p>
<p class="list-page-entry-text achievement-description">Description</p>
<p class="list-page-entry-text achievement-stages">Stages</p>
</div>
<template data-template="achievements-page-list: List<Basic>">
<div class="list-page-entry">
<img class="list-page-entry-icon" src="res/dummy_achievement.png" alt="Achievement Thumbnail"></img>
<div class="list-page-entry-text-section">
<p class="list-page-entry-text achievement-name">${achievement-name}</p>
<p class="list-page-entry-text achievement-description">${achievement-description}</p>
<p class="list-page-entry-text achievement-stages">${stages}</p>
</div>
</div>
</template>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,52 +1,64 @@
<div class="list-page-search"> <div class="page-subsection">
<label for="game-search">Search</label> <div class="list-page-search page-subsection-chunk">
<input id="game-search" type="text" placeholder="Name, Keyword, etc..." game-name="game-search" /> <label for="game-search">Search</label>
</div> <input id="game-search" type="text" placeholder="Name, Keyword, etc..." game-name="game-search" name="game-search"/>
<div class="list-page-partitions">
<div class="list-page-filter-partition">
<p class="page-subheader-text">Filters</p>
<div class="page-subheader-separator"></div>
<div id="games-owned-filter" class="list-page-filter">
<div class="list-page-filter-checkbox"></div>
<p class="list-page-filter-name">Games Owned</p>
</div>
</div> </div>
<div class="list-page-list-partition"> <div class="list-page-partitions">
<div class="list-page-list"> <div class="list-page-filter-partition">
<div class="list-page-header"> <div class="page-subsection-wrapper">
<p class="list-page-entry-icon"></p> <div class="page-subheader">
<p class="list-page-entry-text game-name">Name</p> <p class="page-subheader-text">Filters</p>
<p class="list-page-entry-text game-description">Description</p> <div class="page-subheader-separator"></div>
</div>
<div class="list-page-filter-chunk page-subsection-chunk">
<div class="page-subsection-wrapper">
<div id="games-owned-filter" class="list-page-filter">
<div class="list-page-filter-checkbox"></div>
<p class="list-page-filter-name">Games Owned</p>
</div>
</div>
</div>
</div> </div>
<div class="list-page-entry"> </div>
<img class="list-page-entry-icon" src="res/dummy_game.png" alt="Achievement Icon.png" /> <div class="list-page-list-partition page-subsection-wrapper">
<p class="list-page-entry-text game-name">Latin</p> <div class="page-subsection-chunk">
<p class="list-page-entry-text game-description">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p> <div class="list-page-list">
</div> <div class="list-page-header">
<div class="list-page-entry"> <p class="list-page-entry-icon"></p>
<img class="list-page-entry-icon" src="res/dummy_game.png" alt="Achievement Icon.png" /> <p class="list-page-entry-text game-name">Name</p>
<p class="list-page-entry-text game-name">Latin</p> <p class="list-page-entry-text game-description">Description</p>
<p class="list-page-entry-text game-description">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p> </div>
</div> <div class="list-page-entry">
<div class="list-page-entry"> <img class="list-page-entry-icon" src="res/dummy_game.png" alt="Achievement Icon.png" />
<img class="list-page-entry-icon" src="res/dummy_game.png" alt="Achievement Icon.png" /> <p class="list-page-entry-text game-name">Latin</p>
<p class="list-page-entry-text game-name">Latin</p> <p class="list-page-entry-text game-description">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<p class="list-page-entry-text game-description">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p> </div>
</div> <div class="list-page-entry">
<div class="list-page-entry"> <img class="list-page-entry-icon" src="res/dummy_game.png" alt="Achievement Icon.png" />
<img class="list-page-entry-icon" src="res/dummy_game.png" alt="Achievement Icon.png" /> <p class="list-page-entry-text game-name">Latin</p>
<p class="list-page-entry-text game-name">Latin</p> <p class="list-page-entry-text game-description">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<p class="list-page-entry-text game-description">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p> </div>
</div> <div class="list-page-entry">
<div class="list-page-entry"> <img class="list-page-entry-icon" src="res/dummy_game.png" alt="Achievement Icon.png" />
<img class="list-page-entry-icon" src="res/dummy_game.png" alt="Achievement Icon.png" /> <p class="list-page-entry-text game-name">Latin</p>
<p class="list-page-entry-text game-name">Latin</p> <p class="list-page-entry-text game-description">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<p class="list-page-entry-text game-description">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p> </div>
</div> <div class="list-page-entry">
<div class="list-page-entry"> <img class="list-page-entry-icon" src="res/dummy_game.png" alt="Achievement Icon.png" />
<img class="list-page-entry-icon" src="res/dummy_game.png" alt="Achievement Icon.png" /> <p class="list-page-entry-text game-name">Latin</p>
<p class="list-page-entry-text game-name">Latin</p> <p class="list-page-entry-text game-description">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<p class="list-page-entry-text game-description">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p> </div>
<div class="list-page-entry">
<img class="list-page-entry-icon" src="res/dummy_game.png" alt="Achievement Icon.png" />
<p class="list-page-entry-text game-name">Latin</p>
<p class="list-page-entry-text game-description">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
</div>
<div class="list-page-entry">
<img class="list-page-entry-icon" src="res/dummy_game.png" alt="Achievement Icon.png" />
<p class="list-page-entry-text game-name">Latin</p>
<p class="list-page-entry-text game-description">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,37 +1,37 @@
<div id="profile-section-1"> <div id="profile-section-1">
<div id="profile-info"> <div id="profile-info" class="page-subsection">
<img id="profile-info-pfp" src="res/temp_pfp.png" alt="User's Profile Pictuer" /> <div class="page-subsection-wrapper">
<div class="page-subheader-separator"></div> <div class="page-subheader">
<p id="profile-info-name">Jane Doe</p> <p id="profile-info-name" class="page-subheader-text">Jane Doe</p>
<div class="page-subheader-separator"></div>
</div>
<div id="profile-info-pfp-border" class="page-subsection-chunk">
<div id="profile-info-pfp-vignette">
<img id="profile-info-pfp-img" src="res/guest_pfp.png" alt="User's Profile Pictuer" />
</div>
</div>
</div>
</div> </div>
<div id="profile-platforms"> <div id="profile-platforms" class="page-subsection">
<p class="page-subheader-text">Platforms</p> <div class="page-subsection-wrapper">
<div class="page-subheader-separator"></div> <div class="page-subheader">
<div class="profile-list"> <p class="page-subheader-text">Platforms</p>
<div class="profile-entry accented"> <div class="page-subheader-separator"></div>
<div class="profile-entry-left"> </div>
<div class="profile-list page-subsection-chunk">
<div class="profile-entry accented top">
<img class="profile-entry-icon" src="res/steam.png" alt="Steam Logo" /> <img class="profile-entry-icon" src="res/steam.png" alt="Steam Logo" />
<p class="profile-entry-text">Steam</p> <p class="profile-entry-text platform-name">Steam</p>
</div>
<div class="profile-entry-right">
<p class="profile-entry-text accented">Connected</p> <p class="profile-entry-text accented">Connected</p>
</div> </div>
</div> <div class="profile-entry">
<div class="profile-entry">
<div class="profile-entry-left">
<img class="profile-entry-icon" src="res/xbox.png" alt="Xbox Logo" /> <img class="profile-entry-icon" src="res/xbox.png" alt="Xbox Logo" />
<p class="profile-entry-text">Xbox Live</p> <p class="profile-entry-text platform-name">Xbox Live</p>
</div>
<div class="profile-entry-right">
<p class="profile-entry-text accented">Connected</p> <p class="profile-entry-text accented">Connected</p>
</div> </div>
</div> <div class="profile-entry">
<div class="profile-entry">
<div class="profile-entry-left">
<img class="profile-entry-icon" src="res/psn.png" alt="PSN Logo" /> <img class="profile-entry-icon" src="res/psn.png" alt="PSN Logo" />
<p class="profile-entry-text">PSN</p> <p class="profile-entry-text platform-name">PSN</p>
</div>
<div class="profile-entry-right">
<p class="profile-entry-text accented">Connected</p> <p class="profile-entry-text accented">Connected</p>
</div> </div>
</div> </div>
@ -39,109 +39,130 @@
</div> </div>
</div> </div>
<div id="profile-section-2"> <div id="profile-section-2">
<div id="profile-games"> <div id="profile-games" class="page-subsection">
<p class="page-subheader-text link">Games</p> <div class="page-subsection-wrapper">
<div class="page-subheader-separator"></div> <div class="page-subheader">
<div class="profile-list"> <p class="page-subheader-text link">Games</p>
<div class="profile-entry game"> <div class="page-subheader-separator"></div>
<div class="profile-entry-left">
<img class="profile-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" />
<p class="profile-entry-text">Latin</p>
</div>
</div> </div>
<div class="profile-entry game"> <div class="profile-list page-subsection-chunk">
<div class="profile-entry-left"> <div class="profile-entry game top">
<img class="profile-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" /> <img class="profile-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" />
<p class="profile-entry-text">Latin</p> <p class="profile-entry-text game-name">Latin</p>
</div> </div>
</div> <div class="profile-entry game">
<div class="profile-entry game">
<div class="profile-entry-left">
<img class="profile-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" /> <img class="profile-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" />
<p class="profile-entry-text">Latin</p> <p class="profile-entry-text game-name">Latin</p>
</div> </div>
</div> <div class="profile-entry game">
<div class="profile-entry game">
<div class="profile-entry-left">
<img class="profile-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" /> <img class="profile-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" />
<p class="profile-entry-text">Latin</p> <p class="profile-entry-text game-name">Latin</p>
</div>
<div class="profile-entry game">
<img class="profile-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" />
<p class="profile-entry-text game-name">Latin</p>
</div>
<div class="profile-entry game">
<img class="profile-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" />
<p class="profile-entry-text game-name">Latin</p>
</div>
<div class="profile-entry game">
<img class="profile-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" />
<p class="profile-entry-text game-name">Latin</p>
</div>
<div class="profile-entry game">
<img class="profile-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" />
<p class="profile-entry-text game-name">Latin</p>
</div>
<div class="profile-entry game">
<img class="profile-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" />
<p class="profile-entry-text game-name">Latin</p>
</div>
<div class="profile-entry game">
<img class="profile-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" />
<p class="profile-entry-text game-name">Latin</p>
</div>
<div class="profile-entry game">
<img class="profile-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" />
<p class="profile-entry-text game-name">Latin</p>
</div>
<div class="profile-entry game">
<img class="profile-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" />
<p class="profile-entry-text game-name">Latin</p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div id="profile-achievements"> <div id="profile-achievements" class="page-subsection">
<p class="page-subheader-text link">Achievements</p> <div class="page-subsection-wrapper">
<div class="page-subheader-separator"></div> <div class="page-subheader">
<div class="profile-list"> <p class="page-subheader-text link">Achievements</p>
<div class="profile-entry accented"> <div class="page-subheader-separator"></div>
<div class="profile-entry-left"> </div>
<div class="profile-list page-subsection-chunk">
<div class="profile-entry accented top">
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" /> <img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
<p class="profile-entry-text">Lorem Ipsum</p> <p class="profile-entry-text achievement-name">Lorem Ipsum</p>
</div>
<div class="profile-entry-right">
<p class="profile-entry-text accented">Completed</p> <p class="profile-entry-text accented">Completed</p>
</div> </div>
</div> <div class="profile-entry accented">
<div class="profile-entry accented">
<div class="profile-entry-left">
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" /> <img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
<p class="profile-entry-text">Lorem Ipsum</p> <p class="profile-entry-text achievement-name">Lorem Ipsum</p>
</div>
<div class="profile-entry-right">
<p class="profile-entry-text accented">Completed</p> <p class="profile-entry-text accented">Completed</p>
</div> </div>
</div> <div class="profile-entry accented">
<div class="profile-entry accented">
<div class="profile-entry-left">
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" /> <img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
<p class="profile-entry-text">Lorem Ipsum</p> <p class="profile-entry-text achievement-name">Lorem Ipsum</p>
</div>
<div class="profile-entry-right">
<p class="profile-entry-text accented">Completed</p> <p class="profile-entry-text accented">Completed</p>
</div> </div>
</div> <div class="profile-entry accented">
<div class="profile-entry">
<div class="profile-entry-left">
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" /> <img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
<p class="profile-entry-text">Lorem Ipsum</p> <p class="profile-entry-text achievement-name">Lorem Ipsum</p>
</div>
<div class="profile-entry-right">
<p class="profile-entry-text accented">Completed</p> <p class="profile-entry-text accented">Completed</p>
</div> </div>
</div> <div class="profile-entry accented">
<div class="profile-entry">
<div class="profile-entry-left">
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" /> <img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
<p class="profile-entry-text">Lorem Ipsum</p> <p class="profile-entry-text achievement-name">Lorem Ipsum</p>
</div>
<div class="profile-entry-right">
<p class="profile-entry-text accented">Completed</p> <p class="profile-entry-text accented">Completed</p>
</div> </div>
</div> <div class="profile-entry accented">
<div class="profile-entry">
<div class="profile-entry-left">
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" /> <img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
<p class="profile-entry-text">Lorem Ipsum</p> <p class="profile-entry-text achievement-name">Lorem Ipsum</p>
</div>
<div class="profile-entry-right">
<p class="profile-entry-text accented">Completed</p> <p class="profile-entry-text accented">Completed</p>
</div> </div>
</div> <div class="profile-entry accented">
<div class="profile-entry">
<div class="profile-entry-left">
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" /> <img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
<p class="profile-entry-text">Lorem Ipsum</p> <p class="profile-entry-text achievement-name">Lorem Ipsum</p>
</div>
<div class="profile-entry-right">
<p class="profile-entry-text accented">Completed</p> <p class="profile-entry-text accented">Completed</p>
</div> </div>
</div> <div class="profile-entry">
<div class="profile-entry">
<div class="profile-entry-left">
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" /> <img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
<p class="profile-entry-text">Lorem Ipsum</p> <p class="profile-entry-text achievement-name">Lorem Ipsum</p>
<p class="profile-entry-text accented">Completed</p>
</div> </div>
<div class="profile-entry-right"> <div class="profile-entry">
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
<p class="profile-entry-text achievement-name">Lorem Ipsum</p>
<p class="profile-entry-text accented">Completed</p>
</div>
<div class="profile-entry">
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
<p class="profile-entry-text achievement-name">Lorem Ipsum</p>
<p class="profile-entry-text accented">Completed</p>
</div>
<div class="profile-entry">
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
<p class="profile-entry-text achievement-name">Lorem Ipsum</p>
<p class="profile-entry-text accented">Completed</p>
</div>
<div class="profile-entry">
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
<p class="profile-entry-text achievement-name">Lorem Ipsum</p>
<p class="profile-entry-text accented">Completed</p>
</div>
<div class="profile-entry">
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
<p class="profile-entry-text achievement-name">Lorem Ipsum</p>
<p class="profile-entry-text accented">Completed</p> <p class="profile-entry-text accented">Completed</p>
</div> </div>
</div> </div>

View file

@ -32,7 +32,8 @@ CREATE TABLE [User] (
Email VARCHAR(254) NOT NULL, Email VARCHAR(254) NOT NULL,
Username VARCHAR(32) NOT NULL, Username VARCHAR(32) NOT NULL,
[Password] CHAR(64) NOT NULL, [Password] CHAR(64) NOT NULL,
[Salt] CHAR(32) NOT NULL [Salt] CHAR(32) NOT NULL,
Verified BIT NOT NULL CONSTRAINT VerifiedDefault DEFAULT 0
PRIMARY KEY(ID) PRIMARY KEY(ID)
) )

Binary file not shown.

4
sql/GetUserLogin.sql Normal file
View file

@ -0,0 +1,4 @@
CREATE PROCEDURE GetUserLogin(
@email VARCHAR(254)
) AS
SELECT Id, Salt, [Password] FROM [User] WHERE Email = @email

1187
tmp/unknown.ai Normal file

File diff suppressed because one or more lines are too long

BIN
tmp/unknown.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB