formatting, readme, webp images, updated officers

- added prettier config
- added missing officers
- reordered officers based on official roster
- removed unused assets
- added project description
- fixed mismatching licenses
- optimized images to webp
    - reduced project size by 95%
This commit is contained in:
Raymond Wang 2022-10-22 12:27:16 -07:00
parent 1d9072030e
commit 3dc2eef6e1
54 changed files with 3882 additions and 939 deletions

2
.gitignore vendored
View file

@ -1,3 +1,3 @@
build
dist
node_modules
node_modules

4
.prettierrc Normal file
View file

@ -0,0 +1,4 @@
{
"tabWidth": 4,
"useTabs": false
}

View file

@ -1,2 +1,27 @@
# NewWebsite
The new IEEE website for the section at UCSD
# IEEE @ UCSD Website
## Deployment
Github Actions have been set to trigger on each push and pull request on the `main` branch. The website will be built as static pages to the `page` branch where it will be deployed onto [ieeeucsd.edu](https://ieeeucsd.edu).
## Contributing
Please create a new branch for development (i.e. `[NAME]-dev`). Pushing directly to main is not advised, as changes will go straight into production.
### Testing
To build the site, run `npm run build`.
To view the site on your local network, run `npm build/index.js`. View the site at [localhost:9000](http://localhost:9000).
### Images
Large images should be in WebP format. Consider resizing images based on their usage.
[Squoosh](https://squoosh.app/) is a great online tool for optimizing images developed by Google Chrome Labs.
### Formatting
Install [prettier](https://prettier.io/) and use it as the default Typescript and Javascript formatter. The `.prettierrc` configuration file controls the formatting rules.
For CSS, use `CSS Language Features` as the default formatter. For HTML, use `HTML Language Features` as the default formatter.

2547
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,10 +1,10 @@
{
"name": "ieee-website",
"version": "1.0.0",
"description": "",
"description": "Website for IEEE of UC San Diego",
"main": "build/index.js",
"scripts": {
"build": "webpack & tsc & time /T",
"build": "webpack & tsc",
"watch": "npm-watch build"
},
"watch": {
@ -19,7 +19,7 @@
},
"keywords": [],
"author": "",
"license": "ISC",
"license": "MIT",
"dependencies": {
"@types/express": "^4.17.13",
"@types/node": "^16.11.7",
@ -35,15 +35,22 @@
"@babel/preset-env": "^7.15.8",
"@babel/preset-react": "^7.14.5",
"@types/react": "^16.14.20",
"@typescript-eslint/eslint-plugin": "^5.40.1",
"babel-loader": "^8.2.3",
"copy-webpack-plugin": "^6.4.1",
"css-loader": "^5.2.7",
"eslint": "^8.26.0",
"eslint-config-standard-with-typescript": "^23.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-n": "^15.3.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-react": "^7.31.10",
"html-webpack-plugin": "^4.5.2",
"license-webpack-plugin": "^2.3.21",
"style-loader": "^2.0.0",
"ts-loader": "^8.3.0",
"typescript": "^4.4.4",
"typescript": "^4.8.4",
"webpack": "^5.60.0",
"webpack-cli": "^4.9.1"
}
}
}

View file

@ -1,208 +1,236 @@
import {Schema, Document, LeanDocument} from "mongoose";
import { Schema, Document, LeanDocument } from "mongoose";
import * as mongoose from "mongoose";
import {promisify} from "util";
import {randomBytes, pbkdf2} from "crypto";
import { promisify } from "util";
import { randomBytes, pbkdf2 } from "crypto";
const pbkdf = promisify(pbkdf2);
const User = mongoose.model("User", new Schema({
display: String,
email: {
public: Boolean,
text: String,
},
picture: String,
password: {
hash: Buffer,
salt: Buffer
},
bio: {type: String, default: ""},
isOfficer: {type: Boolean, default: false},
title: {type: String, default: "Member"},
uuid: String
}));
const User = mongoose.model(
"User",
new Schema({
display: String,
email: {
public: Boolean,
text: String,
},
picture: String,
password: {
hash: Buffer,
salt: Buffer,
},
bio: { type: String, default: "" },
isOfficer: { type: Boolean, default: false },
title: { type: String, default: "Member" },
uuid: String,
})
);
export interface DBUser extends Document {
display: string;
email: string;
picture: string;
password: HashSalt
isOfficer: boolean;
title: string;
uuid: string;
display: string;
email: string;
picture: string;
password: HashSalt;
isOfficer: boolean;
title: string;
uuid: string;
}
interface HashSalt {
hash: Buffer;
salt: Buffer;
hash: Buffer;
salt: Buffer;
}
export default class UserDatabase {
// How many iterations pbkdf2 will iterate the password hashing algorithm
private static PW_ITERATIONS = 100000;
// The length of the salt that should be used with every password hash
private static SALT_LENGTH = 64;
// How many bytes will be used to generate a UUID of length 2 times this variable
private static UUID_LENGTH = 32;
// How many iterations pbkdf2 will iterate the password hashing algorithm
private static PW_ITERATIONS = 100000;
// The length of the salt that should be used with every password hash
private static SALT_LENGTH = 64;
// How many bytes will be used to generate a UUID of length 2 times this variable
private static UUID_LENGTH = 32;
/**
* Makes a new UserDatabase
* @param url the mongodb database url to connect to
*/
constructor(url: string) {
mongoose.connect(url);
mongoose.set("returnOriginal", false);
}
/**
* Makes a new UserDatabase
* @param url the mongodb database url to connect to
*/
constructor(url: string) {
mongoose.connect(url);
mongoose.set("returnOriginal", false);
}
// Functions to edit current users
/**
* Changes a user's display name on the website
* @param uuid the UUID of the user
* @param newName the user's new name
*/
public async setName(uuid: string, newName: string) {
await User.findOneAndUpdate({ uuid: uuid }, { name: newName }).exec();
}
/**
* Gets the display name of a user with a given UUID
* @param uuid the UUID of the user
* @returns the user's display name
*/
public async getName(uuid: string): Promise<string> {
return ((await User.findOne({ uuid: uuid }).lean().exec()) as DBUser)
.display;
}
// Functions to edit current users
/**
* Changes a user's display name on the website
* @param uuid the UUID of the user
* @param newName the user's new name
*/
public async setName(uuid: string, newName: string) {
await User.findOneAndUpdate({uuid: uuid}, {name: newName}).exec();
}
/**
* Gets the display name of a user with a given UUID
* @param uuid the UUID of the user
* @returns the user's display name
*/
public async getName(uuid: string): Promise<string> {
return (await User.findOne({uuid: uuid}).lean().exec() as DBUser).display;
}
/**
* Updates a user's display email
* @param uuid the UUID of the user
* @param newEmail the user's new display email
*/
public async setEmail(uuid: string, newEmail: string) {
await User.findOneAndUpdate({ uuid: uuid }, { email: newEmail }).exec();
}
/**
* Gets the email of a user with a given UUID
* @param uuid the UUID of the user
* @returns the user's email
*/
public async getEmail(uuid: string): Promise<string> {
return ((await User.findOne({ uuid: uuid }).lean().exec()) as DBUser)
.email;
}
/**
* Updates a user's display email
* @param uuid the UUID of the user
* @param newEmail the user's new display email
*/
public async setEmail(uuid: string, newEmail: string) {
await User.findOneAndUpdate({uuid: uuid}, {email: newEmail}).exec();
}
/**
* Gets the email of a user with a given UUID
* @param uuid the UUID of the user
* @returns the user's email
*/
public async getEmail(uuid: string): Promise<string> {
return (await User.findOne({uuid: uuid}).lean().exec() as DBUser).email;
}
/**
* Updates a user's password
* @param uuid the UUID of the user
* @param newPassword the user's new password
*/
public async setPassword(uuid: string, newPassword: string) {
let hashsalt = await UserDatabase.hash(newPassword);
await User.findOneAndUpdate(
{ uuid: uuid },
{ password: hashsalt }
).exec();
}
/**
* Gets the password of a user with a given UUID
* @param uuid the UUID of the user
* @returns the user's password
*/
public async getPasswordHash(uuid: string): Promise<Buffer> {
return ((await User.findOne({ uuid: uuid }).lean().exec()) as DBUser)
.password.hash;
}
/**
* Updates a user's password
* @param uuid the UUID of the user
* @param newPassword the user's new password
*/
public async setPassword(uuid: string, newPassword: string) {
let hashsalt = await UserDatabase.hash(newPassword);
await User.findOneAndUpdate({uuid: uuid}, {password: hashsalt}).exec();
}
/**
* Gets the password of a user with a given UUID
* @param uuid the UUID of the user
* @returns the user's password
*/
public async getPasswordHash(uuid: string): Promise<Buffer> {
return (await User.findOne({uuid: uuid}).lean().exec() as DBUser).password.hash;
}
/**
* Updates a member's position
* @param uuid the UUID of the user
* @param newTitle the user's new officer title/position
*/
public async setRank(uuid: string, newTitle: string, isOfficer: boolean) {
await User.findOneAndUpdate(
{ uuid: uuid },
{ title: newTitle, isOfficer: isOfficer }
).exec();
}
/**
* Gets the title of a user with a given UUID
* @param uuid the UUID of the user
* @returns the user's title
*/
public async getRank(uuid: string): Promise<string> {
return ((await User.findOne({ uuid: uuid }).lean().exec()) as DBUser)
.title;
}
/**
* Gets whether of a user with a given UUID is an officer
* @param uuid the UUID of the user
* @returns true if the user is an officer
*/
public async isOfficer(uuid: string): Promise<boolean> {
return ((await User.findOne({ uuid: uuid }).lean().exec()) as DBUser)
.isOfficer;
}
/**
* Updates a member's position
* @param uuid the UUID of the user
* @param newTitle the user's new officer title/position
*/
public async setRank(uuid: string, newTitle: string, isOfficer: boolean) {
await User.findOneAndUpdate({uuid: uuid}, {title: newTitle, isOfficer: isOfficer}).exec();
}
/**
* Gets the title of a user with a given UUID
* @param uuid the UUID of the user
* @returns the user's title
*/
public async getRank(uuid: string): Promise<string> {
return (await User.findOne({uuid: uuid}).lean().exec() as DBUser).title;
}
/**
* Gets whether of a user with a given UUID is an officer
* @param uuid the UUID of the user
* @returns true if the user is an officer
*/
public async isOfficer(uuid: string): Promise<boolean> {
return (await User.findOne({uuid: uuid}).lean().exec() as DBUser).isOfficer;
}
/**
* Sets the user's new profile picture
* @param uuid the UUID of the user
* @param newPic the link to the new URL of the profile picture
*/
public async setPicture(uuid: string, newPic: string) {
await User.findOneAndUpdate({ uuid: uuid }, { picture: newPic }).exec();
}
/**
* Gets the profile picture of a user with a given UUID
* @param uuid the UUID of the user
* @returns the user's profile picture
*/
public async getPicture(uuid: string): Promise<string> {
return ((await User.findOne({ uuid: uuid }).lean().exec()) as DBUser)
.picture;
}
/**
* Sets the user's new profile picture
* @param uuid the UUID of the user
* @param newPic the link to the new URL of the profile picture
*/
public async setPicture(uuid: string, newPic: string) {
await User.findOneAndUpdate({uuid: uuid}, {picture: newPic}).exec();
}
/**
* Gets the profile picture of a user with a given UUID
* @param uuid the UUID of the user
* @returns the user's profile picture
*/
public async getPicture(uuid: string): Promise<string> {
return (await User.findOne({uuid: uuid}).lean().exec() as DBUser).picture;
}
public async getUser(uuid: string): Promise<DBUser> {
return User.findOne({ uuid: uuid }).exec() as Promise<DBUser>;
}
public async getUser(uuid: string): Promise<DBUser> {
return User.findOne({uuid: uuid}).exec() as Promise<DBUser>;
}
// User creation and deletion functions
/**
* Deletes a user with the given UUID
* @param uuid the UUID of the user
*/
public async deleteUser(uuid: string) {
await User.findOneAndDelete({ uuid: uuid }).exec();
}
// User creation and deletion functions
/**
* Deletes a user with the given UUID
* @param uuid the UUID of the user
*/
public async deleteUser(uuid: string) {
await User.findOneAndDelete({uuid: uuid}).exec();
}
/**
* Makes a new user and adds it to the database
* @param display the user's display name
* @param email the user's display email
* @param picture the user's picture
* @param password the user's plaintext password
*/
public async makeNewUser(
display: string,
email: string,
picture: string,
password: string,
title?: string,
isOfficer?: boolean
): Promise<void> {
let encrypted = await UserDatabase.hash(password);
await new User({
display: display,
email: email,
picture: picture,
password: encrypted,
uuid: UserDatabase.genUUID(),
isOfficer: !!isOfficer,
title: title ? title : "Member",
}).save();
}
/**
* Makes a new user and adds it to the database
* @param display the user's display name
* @param email the user's display email
* @param picture the user's picture
* @param password the user's plaintext password
*/
public async makeNewUser(display: string, email: string, picture: string, password: string, title?: string, isOfficer?: boolean): Promise<void> {
let encrypted = await UserDatabase.hash(password);
await (new User({
display: display,
email: email,
picture: picture,
password: encrypted,
uuid: UserDatabase.genUUID(),
isOfficer: !!isOfficer,
title: title ? title : "Member"
})).save();
}
// Utility functions
/**
* Hashes a password using a salt of 64 random bytes and pbkdf2 with 100000 iterations
* @param password the password as a string that should be hashed
* @returns the hashed password
*/
public static async hash(password: string): Promise<HashSalt> {
let salt = randomBytes(UserDatabase.SALT_LENGTH);
let pass = Buffer.from(String.prototype.normalize(password));
return {
hash: await pbkdf(
pass,
salt,
UserDatabase.PW_ITERATIONS,
512,
"sha512"
),
salt: salt,
};
}
// Utility functions
/**
* Hashes a password using a salt of 64 random bytes and pbkdf2 with 100000 iterations
* @param password the password as a string that should be hashed
* @returns the hashed password
*/
public static async hash(password: string): Promise<HashSalt> {
let salt = randomBytes(UserDatabase.SALT_LENGTH);
let pass = Buffer.from(String.prototype.normalize(password));
return {
hash: await pbkdf(pass, salt, UserDatabase.PW_ITERATIONS, 512, "sha512"),
salt: salt
};
}
/**
* Generates a UUID for a user or anything else
* @returns a random hex string of length 2 * UUID_LENGTH
*/
public static genUUID(): string {
return Array.from(randomBytes(UserDatabase.UUID_LENGTH))
.map(val=>(val.toString(16).padStart(2,"0"))).join("");
}
}
/**
* Generates a UUID for a user or anything else
* @returns a random hex string of length 2 * UUID_LENGTH
*/
public static genUUID(): string {
return Array.from(randomBytes(UserDatabase.UUID_LENGTH))
.map((val) => val.toString(16).padStart(2, "0"))
.join("");
}
}

View file

@ -1,121 +1,129 @@
import express from "express";
import {Request, Response} from "express";
import { Request, Response } from "express";
import * as path from "path";
import * as fs from "fs";
import UserDatabase from "./Database";
interface Website {
[key: string]: string;
sitename: string;
title: string;
description: string;
jsfile: string;
cssfile: string;
themecolor: string;
[key: string]: string;
sitename: string;
title: string;
description: string;
jsfile: string;
cssfile: string;
themecolor: string;
}
const app = express();
const template = fs.readFileSync(path.join(__dirname, "public/template.html")).toString();
const websites = [{
sitename: "index",
title: "IEEE at UCSD",
description: "",
jsfile: "js/index.js",
cssfile: "css/styles.css",
themecolor: ""
},
{
sitename: "events",
title: "IEEE at UCSD",
description: "",
jsfile: "js/events.js",
cssfile: "css/styles.css",
themecolor: ""
},
{
sitename: "projects",
title: "IEEE at UCSD",
description: "",
jsfile: "js/projects.js",
cssfile: "css/styles.css",
themecolor: ""
},
{
sitename: "committees",
title: "IEEE at UCSD",
description: "",
jsfile: "js/committees.js",
cssfile: "css/styles.css",
themecolor: ""
},
{
sitename: "contact",
title: "IEEE at UCSD",
description: "",
jsfile: "js/index.js",
cssfile: "css/styles.css",
themecolor: ""
}
const APP = express();
const TEMPLATE = fs
.readFileSync(path.join(__dirname, "public/template.html"))
.toString();
const WEBSITES = [
{
sitename: "index",
title: "IEEE at UCSD",
description: "",
jsfile: "js/index.js",
cssfile: "css/styles.css",
themecolor: "",
},
{
sitename: "events",
title: "IEEE at UCSD",
description: "",
jsfile: "js/events.js",
cssfile: "css/styles.css",
themecolor: "",
},
{
sitename: "projects",
title: "IEEE at UCSD",
description: "",
jsfile: "js/projects.js",
cssfile: "css/styles.css",
themecolor: "",
},
{
sitename: "committees",
title: "IEEE at UCSD",
description: "",
jsfile: "js/committees.js",
cssfile: "css/styles.css",
themecolor: "",
},
{
sitename: "contact",
title: "IEEE at UCSD",
description: "",
jsfile: "js/index.js",
cssfile: "css/styles.css",
themecolor: "",
},
] as Website[];
const PORT = 9000;
// Make the public directory traversible to people online
app.use(express.static(path.join(__dirname, "public")));
APP.use(express.static(path.join(__dirname, "public")));
// Put the cookies as a variable in the request
app.use((req: Request, res: Response, next: any)=>{
req.cookies = req.headers.cookie;
next();
APP.use((req: Request, res: Response, next: any) => {
req.cookies = req.headers.cookie;
next();
});
// Receive json post requests and urlencoded requests
app.use(express.json());
app.use(express.urlencoded({extended: true}));
APP.use(express.json());
APP.use(express.urlencoded({ extended: true }));
// Send main page
app.get("/", (req: Request, res: Response) => {
respond(res, "index");
APP.get("/", (req: Request, res: Response) => {
respond(res, "index");
});
app.get("/events", (req: Request, res: Response) => {
respond(res, "events");
APP.get("/events", (req: Request, res: Response) => {
respond(res, "events");
});
app.get("/projects", (req: Request, res: Response) => {
respond(res, "projects");
APP.get("/projects", (req: Request, res: Response) => {
respond(res, "projects");
});
app.get("/committees", (req: Request, res: Response) => {
respond(res, "committees");
APP.get("/committees", (req: Request, res: Response) => {
respond(res, "committees");
});
app.get("/contact", (req: Request, res: Response) => {
respond(res, "contact");
APP.get("/contact", (req: Request, res: Response) => {
respond(res, "contact");
});
/**
* Utility functions for above methods
*/
function respond(res: any, filename: string) {
res.set({
"Content-Type": "text/html"
});
res.send(generatePage(filename));
res.set({
"Content-Type": "text/html",
});
res.send(generatePage(filename));
}
function generatePage(name: string): string {
let site = websites.find(e=>e.sitename===name);
let html = template;
for (let key of Object.keys(site)) {
html = html.replace(new RegExp("\\$" + key.toUpperCase()), site[key]);
}
return html;
let site = WEBSITES.find((e) => e.sitename === name);
let html = TEMPLATE;
for (let key of Object.keys(site)) {
html = html.replace(new RegExp("\\$" + key.toUpperCase()), site[key]);
}
return html;
}
function generateFilePages() {
for (let site of websites) {
let html = generatePage(site.sitename);
fs.writeFileSync(require("path").join(__dirname, "public/", `${site.sitename}.html`), html);
}
for (let site of WEBSITES) {
let html = generatePage(site.sitename);
fs.writeFileSync(
require("path").join(__dirname, "public/", `${site.sitename}.html`),
html
);
}
}
if (process.argv[2] === "generate") {
generateFilePages();
generateFilePages();
} else {
app.listen(PORT, "127.0.0.1");
}
APP.listen(PORT, () => {
console.log(`Listening on port ${PORT}`);
});
}

View file

@ -1,171 +1,237 @@
// The links that are on the top nav bar that link to a lowercase version of their page
export const ACTIVE_PAGES = [{
name: "Home",
url: "/"
}, {
name: "Events",
url: "/events"
}, {
name: "Projects",
url: "/projects"
}, {
name: "Committees",
url: "/committees"
}, {
name: "Contact Us",
url: "/contact"
}];
export const ACTIVE_PAGES = [
{
name: "Home",
url: "/",
},
{
name: "Events",
url: "/events",
},
{
name: "Projects",
url: "/projects",
},
{
name: "Committees",
url: "/committees",
},
{
name: "Contact Us",
url: "/contact",
},
];
// Urls of team photos that will go in the "About" slideshow
export const TEAM_PHOTOS: string[] = [];
export const PROJECT_SPACE: string[] = [];
export const EVENTS: any[] = [];
export const SOCIALS = [
{
icon: "img/disc.svg",
url: "https://discord.gg/XxfjqZSjca",
message: "Join our server",
},
{
icon: "img/fab.svg",
url: "https://www.facebook.com/ieeeucsd",
message: "ieeeucsd",
},
{
icon: "img/inst.svg",
url: "https://www.instagram.com/ieee.ucsd",
message: "@ieeeucsd",
},
];
export const EMAIL = [
{
icon: "img/email.svg",
url: "mailto:ieee@eng.ucsd.edu",
message: "ieee@eng.ucsd.edu",
},
];
export const EMAIL_WHITE = [
{
icon: "img/email_white.svg",
url: "mailto:ieee@eng.ucsd.edu",
message: "ieee@eng.ucsd.edu",
},
];
export const SOCIALS = [{
icon: "img/disc.svg",
url: "https://discord.gg/XxfjqZSjca",
message: "Join our server"
}, {
icon: "img/fab.svg",
url: "https://www.facebook.com/ieeeucsd",
message: "ieeeucsd"
}, {
icon: "img/inst.svg",
url: "https://www.instagram.com/ieee.ucsd",
message: "@ieeeucsd"
}];
export const EMAIL = [{
icon: "img/email.svg",
url: "mailto:ieee@eng.ucsd.edu",
message: "ieee@eng.ucsd.edu"
}];
export const EMAIL_WHITE = [{
icon: "img/email_white.svg",
url: "mailto:ieee@eng.ucsd.edu",
message: "ieee@eng.ucsd.edu"
}]
export const SOCIALS_WHITE = [
{
icon: "img/disc_white.svg",
url: "https://discord.gg/XxfjqZSjca",
message: "Join our server",
},
{
icon: "img/fab_white.svg",
url: "https://www.facebook.com/ieeeucsd",
message: "ieeeucsd",
},
{
icon: "img/inst_white.svg",
url: "https://www.instagram.com/ieee.ucsd",
message: "@ieeeucsd",
},
];
export const SOCIALS_WHITE = [{
icon: "img/disc_white.svg",
url: "https://discord.gg/XxfjqZSjca",
message: "Join our server"
}, {
icon: "img/fab_white.svg",
url: "https://www.facebook.com/ieeeucsd",
message: "ieeeucsd"
}, {
icon: "img/inst_white.svg",
url: "https://www.instagram.com/ieee.ucsd",
message: "@ieeeucsd"
}];
export const OFFICERS = [{
name: "Darin Tsui",
position: "Chair",
photo: "img/officers/darin.jpg",
email: "email@ucsd.edu"
}, {
name: "Brigette Hacia",
position: "Vice Chair Internal",
photo: "img/officers/brigette.jpg",
email: "email@ucsd.edu"
}, {
name: "Tasnia Jamal",
position: "Vice Chair Events",
photo: "img/officers/tasnia.jpg",
email: "email@ucsd.edu"
}, {
name: "Kevin Chang",
position: "Vice Chair Projects",
photo: "img/officers/kevin.jpg",
email: "email@ucsd.edu"
}, {
name: "Niklas Chang",
position: "Events Coordinator",
photo: "img/officers/niklas.jpg",
email: "email@ucsd.edu"
}, {
name: "Tien Vu",
position: "Vice Chair External",
photo: "img/officers/tien.jpg",
email: "email@ucsd.edu"
}, {
name: "Arjun Sampath",
position: "Vice Chair Finance",
photo: "img/officers/arjun.jpg",
email: "email@ucsd.edu"
}, {
name: "Derek Nguyen",
position: "Project Space Chair",
photo: "img/officers/derek.jpg",
email: "email@ucsd.edu"
}, {
name: "Mohak Vaswani",
position: "Technical Chair",
photo: "img/officers/mohak.jpg",
email: "email@ucsd.edu"
}, {
name: "Parisa Shahabi",
position: "Social Chair",
photo: "img/officers/parisa.jpg",
email: "email@ucsd.edu"
}, {
name: "Vuong Bui",
position: "Professional Chair",
photo: "img/officers/vuong.jpg",
email: "email@ucsd.edu"
}, {
name: "Jason Liang",
position: "Professional Chair",
photo: "img/officers/jason.jpg",
email: "email@ucsd.edu"
}, {
name: "Daniel Chen",
position: "Outreach Chair",
photo: "img/officers/daniel.jpg",
email: "email@ucsd.edu"
}, {
name: "Dennis Liang",
position: "Outreach Chair",
photo: "img/officers/dennis.jpg",
email: "email@ucsd.edu"
}, {
name: "Yash Puneet",
position: "Robocup Chair",
photo: "img/officers/yash.jpg",
email: "email@ucsd.edu"
}, {
name: "Rafaella Gomes",
position: "Robocup Chair",
photo: "img/officers/rafaella.jpg",
email: "email@ucsd.edu"
}, {
name: "Josh Lapidario",
position: "Quarterly Projects Chair",
photo: "img/officers/josh.jpg",
email: "email@ucsd.edu"
}, {
name: "Jiliana Tiu",
position: "Webmaster",
photo: "img/officers/jiliana.jpg",
email: "email@ucsd.edu"
}, {
name: "Stephanie Xu",
position: "PR Chair - Graphics",
photo: "img/officers/stephanie.jpg",
email: "email@ucsd.edu"
}, {
name: "Sankalp Kaushik",
position: "PR Chair - Media",
photo: "img/officers/sankalp.jpg",
email: "email@ucsd.edu"
}, {
name: "Matthew Mikhailov",
position: "Supercomputing Chair",
photo: "img/officers/temp.png",
email: "email@ucsd.edu"
}, {
name: "Raymond Wang",
position: "Webmaster",
photo: "img/officers/temp.png",
email: "raymond@ucsd.edu"
}];
export const OFFICERS = [
{
name: "Darin Tsui",
position: "Chair",
photo: "img/officers/darin.jpg",
email: "email@ucsd.edu",
},
{
name: "Brigette Hacia",
position: "Vice Chair Internal",
photo: "img/officers/brigette.jpg",
email: "email@ucsd.edu",
},
{
name: "Tasnia Jamal",
position: "Vice Chair Events",
photo: "img/officers/tasnia.jpg",
email: "email@ucsd.edu",
},
{
name: "Kevin Chang",
position: "Vice Chair Projects",
photo: "img/officers/kevin.jpg",
email: "email@ucsd.edu",
},
{
name: "Arjun Sampath",
position: "Vice Chair Finance",
photo: "img/officers/arjun.jpg",
email: "email@ucsd.edu",
},
{
name: "Niklas Chang",
position: "Events Coordinator",
photo: "img/officers/niklas.jpg",
email: "email@ucsd.edu",
},
{
name: "Tien Vu",
position: "Vice Chair External",
photo: "img/officers/tien.jpg",
email: "email@ucsd.edu",
},
{
name: "Derek Nguyen",
position: "Project Space Chair",
photo: "img/officers/derek.jpg",
email: "email@ucsd.edu",
},
{
name: "Rafaella Gomes",
position: "Robocup Chair",
photo: "img/officers/rafaella.jpg",
email: "email@ucsd.edu",
},
{
name: "Yash Puneet",
position: "Robocup Chair",
photo: "img/officers/yash.jpg",
email: "email@ucsd.edu",
},
{
name: "Matthew Mikhailov",
position: "Supercomputing Chair",
photo: "img/officers/temp.png",
email: "email@ucsd.edu",
},
{
name: "Josh Lapidario",
position: "Quarterly Projects Chair",
photo: "img/officers/josh.jpg",
email: "email@ucsd.edu",
},
{
name: "Sanh Nguyen",
position: "Quarterly Projects Chair",
photo: "img/officers/temp.png",
email: "email@ucsd.edu",
},
{
name: "Vuong Bui",
position: "Professional Chair",
photo: "img/officers/vuong.jpg",
email: "email@ucsd.edu",
},
{
name: "Jason Liang",
position: "Professional Chair",
photo: "img/officers/jason.jpg",
email: "email@ucsd.edu",
},
{
name: "Mohak Vaswani",
position: "Technical Chair",
photo: "img/officers/mohak.jpg",
email: "email@ucsd.edu",
},
{
name: "Yusuf Morsi",
position: "Technical Chair",
photo: "img/officers/temp.png",
email: "email@ucsd.edu",
},
{
name: "Shaun Garcia",
position: "Technical Chair",
photo: "img/officers/temp.png",
email: "email@ucsd.edu",
},
{
name: "Dennis Liang",
position: "Outreach Chair",
photo: "img/officers/dennis.jpg",
email: "email@ucsd.edu",
},
{
name: "Daniel Chen",
position: "Outreach Chair",
photo: "img/officers/daniel.jpg",
email: "email@ucsd.edu",
},
{
name: "Parisa Shahabi",
position: "Social Chair",
photo: "img/officers/parisa.jpg",
email: "email@ucsd.edu",
},
{
name: "Matthew Yik",
position: "Social Chair",
photo: "img/officers/temp.png",
email: "email@ucsd.edu",
},
{
name: "Jiliana Tiu",
position: "Webmaster",
photo: "img/officers/jiliana.jpg",
email: "email@ucsd.edu",
},
{
name: "Raymond Wang",
position: "Webmaster",
photo: "img/officers/temp.png",
email: "raymond@ucsd.edu",
},
{
name: "Sankalp Kaushik",
position: "PR Chair - Media",
photo: "img/officers/sankalp.jpg",
email: "email@ucsd.edu",
},
{
name: "Stephanie Xu",
position: "PR Chair - Graphics",
photo: "img/officers/stephanie.jpg",
email: "email@ucsd.edu",
},
];

View file

@ -1,7 +1,7 @@
import * as ReactDom from "react-dom";
import * as React from "react";
import TopBar from "./components/TopBar";
import {ACTIVE_PAGES, SOCIALS, EMAIL, OFFICERS} from "./Config";
import { ACTIVE_PAGES, SOCIALS, EMAIL, OFFICERS } from "./Config";
import Splash from "./components/Splash";
import DefaultSection from "./components/DefaultSection";
import InvolveBox from "./components/InvolveBox";
@ -13,46 +13,81 @@ interface MainProps {}
interface MainState {}
class Main extends React.Component<MainProps, MainState> {
constructor(props: MainProps) {
super(props);
this.state = {};
}
public render() {
return <>
<TopBar links={ACTIVE_PAGES}></TopBar>
<Splash cta="The backbone of IEEE. Come help make a difference!" delay={2000} backgrounds={["img/backgrounds/committee.png"]}></Splash>
<DefaultSection title="Join us!" paragraphs={[
"Interested in gaining experience of event planning and development, meeting new friends, and learning more about IEEE? Join one of our committees as an IEEEntern!"
]}>
</DefaultSection>
<DefaultSection className={"our-comms"} title="Our Committees">
<div className="cards">
<InvolveBox boxTitle="" image="img/committees/technical.jpg" description="Technical Committee"></InvolveBox>
<InvolveBox boxTitle="" image="img/committees/social.jpg" description="Social Committee"></InvolveBox>
<InvolveBox boxTitle="" image="img/committees/professional.jpg" description="Professional Committee"></InvolveBox>
<InvolveBox boxTitle="" image="img/committees/pr.jpg" description="PR Committee"></InvolveBox>
<InvolveBox boxTitle="" image="img/committees/outreach.jpg" description="Outreach Committee"></InvolveBox>
</div>
</DefaultSection>
<div id="contact-us">
<DefaultSection title="Interested? Join our socials!">
<div className="join-scls">{
[...EMAIL, ...SOCIALS].map(n => (
<SocialCard url={n.url} image={n.icon} message={n.message}></SocialCard>
))
}</div>
</DefaultSection>
<DefaultSection className="contact" title="Or... Contact one of our staff!">
<Carousel items={OFFICERS} itemsPerPage={6}></Carousel>
</DefaultSection>
</div>
constructor(props: MainProps) {
super(props);
this.state = {};
}
public render() {
return (
<>
<TopBar links={ACTIVE_PAGES}></TopBar>
<Splash
cta="The backbone of IEEE. Come help make a difference!"
delay={2000}
backgrounds={["img/backgrounds/committee.webp"]}
></Splash>
<DefaultSection
title="Join us!"
paragraphs={[
"Interested in gaining experience of event planning and development, meeting new friends, and learning more about IEEE? Join one of our committees as an IEEEntern!",
]}
></DefaultSection>
<DefaultSection className={"our-comms"} title="Our Committees">
<div className="cards">
<InvolveBox
boxTitle=""
image="img/committees/technical.webp"
description="Technical Committee"
></InvolveBox>
<InvolveBox
boxTitle=""
image="img/committees/social.webp"
description="Social Committee"
></InvolveBox>
<InvolveBox
boxTitle=""
image="img/committees/professional.webp"
description="Professional Committee"
></InvolveBox>
<InvolveBox
boxTitle=""
image="img/committees/pr.webp"
description="PR Committee"
></InvolveBox>
<InvolveBox
boxTitle=""
image="img/committees/outreach.webp"
description="Outreach Committee"
></InvolveBox>
</div>
</DefaultSection>
<Footer></Footer>
</>;
}
<div id="contact-us">
<DefaultSection title="Interested? Join our socials!">
<div className="join-scls">
{[...EMAIL, ...SOCIALS].map((n) => (
<SocialCard
url={n.url}
image={n.icon}
message={n.message}
></SocialCard>
))}
</div>
</DefaultSection>
<DefaultSection
className="contact"
title="Or... Contact one of our staff!"
>
<Carousel items={OFFICERS} itemsPerPage={6}></Carousel>
</DefaultSection>
</div>
<Footer></Footer>
</>
);
}
}
ReactDom.render(<Main></Main>, document.getElementById("root"));
export default {};
export default {};

View file

@ -1,57 +1,81 @@
import * as React from "react";
import {Component} from "react";
import { Component } from "react";
import { CarouselItemProps } from "./CarouselItem";
import CarouselPage from "./CarouselPage";
interface CarouselProps {
items: CarouselItemProps[];
itemsPerPage: number;
items: CarouselItemProps[];
itemsPerPage: number;
}
interface CarouselState {
page: number;
page: number;
}
export default class Carousel extends Component<CarouselProps, CarouselState> {
constructor(props: CarouselProps) {
super(props);
this.state = {
page: 0
};
}
constructor(props: CarouselProps) {
super(props);
this.state = {
page: 0,
};
}
private prevPage() {
this.setState({page: this.state.page - 1 < 0 ? 0 : this.state.page - 1});
}
private prevPage() {
this.setState({
page: this.state.page - 1 < 0 ? 0 : this.state.page - 1,
});
}
private nextPage() {
this.setState({ page: this.state.page + 1 > Math.ceil(this.props.items.length / this.props.itemsPerPage) - 1 ?
Math.ceil(this.props.items.length / this.props.itemsPerPage) - 1 :
this.state.page + 1
});
}
private nextPage() {
this.setState({
page:
this.state.page + 1 >
Math.ceil(this.props.items.length / this.props.itemsPerPage) - 1
? Math.ceil(
this.props.items.length / this.props.itemsPerPage
) - 1
: this.state.page + 1,
});
}
public render() {
let arr = this.chunkArray(this.props.items);
return <div className="carousel">
<img className="carousel-left" src="img/arrow.svg" style={
this.state.page === 0 ? {visibility: "hidden"} : {}
} onClick={this.prevPage.bind(this)}></img>
<div className="carousel-items">{
arr.map((items, i)=>(
<CarouselPage items={items} visible={i===this.state.page}></CarouselPage>
))
}</div>
<img className="carousel-right" src="img/arrow.svg" style={
this.state.page === arr.length - 1 ? {visibility: "hidden"} : {}
} onClick={this.nextPage.bind(this)}></img>
</div>;
}
public render() {
let arr = this.chunkArray(this.props.items);
return (
<div className="carousel">
<img
className="carousel-left"
src="img/arrow.svg"
style={
this.state.page === 0 ? { visibility: "hidden" } : {}
}
onClick={this.prevPage.bind(this)}
></img>
<div className="carousel-items">
{arr.map((items, i) => (
<CarouselPage
items={items}
visible={i === this.state.page}
></CarouselPage>
))}
</div>
<img
className="carousel-right"
src="img/arrow.svg"
style={
this.state.page === arr.length - 1
? { visibility: "hidden" }
: {}
}
onClick={this.nextPage.bind(this)}
></img>
</div>
);
}
private chunkArray(array: CarouselItemProps[]):CarouselItemProps[][] {
let returnArr = [] as CarouselItemProps[][];
for(let i = 0; i < array.length; i+=this.props.itemsPerPage) {
returnArr.push(array.slice(i, i + this.props.itemsPerPage));
}
return returnArr;
}
}
private chunkArray(array: CarouselItemProps[]): CarouselItemProps[][] {
let returnArr = [] as CarouselItemProps[][];
for (let i = 0; i < array.length; i += this.props.itemsPerPage) {
returnArr.push(array.slice(i, i + this.props.itemsPerPage));
}
return returnArr;
}
}

View file

@ -1,25 +1,30 @@
import * as React from "react";
import {Component} from "react";
import { Component } from "react";
export interface CarouselItemProps {
name: string;
position: string;
email: string;
photo: string;
name: string;
position: string;
email: string;
photo: string;
}
interface CarouselItemState {}
export default class CarouselItem extends Component<CarouselItemProps, CarouselItemState> {
constructor(props: CarouselItemProps) {
super(props);
this.state = {};
}
export default class CarouselItem extends Component<
CarouselItemProps,
CarouselItemState
> {
constructor(props: CarouselItemProps) {
super(props);
this.state = {};
}
public render() {
return <div className="carousel-item">
<img src={this.props.photo}></img>
<div className="carousel-name">{this.props.name}</div>
<div className="carousel-pos">{this.props.position}</div>
</div>;
}
public render() {
return (
<div className="carousel-item">
<img src={this.props.photo}></img>
<div className="carousel-name">{this.props.name}</div>
<div className="carousel-pos">{this.props.position}</div>
</div>
);
}
}

View file

@ -1,24 +1,32 @@
import * as React from "react";
import {Component} from "react";
import { Component } from "react";
import CarouselItem, { CarouselItemProps } from "./CarouselItem";
interface CarouselPageProps {
items: CarouselItemProps[];
visible: boolean;
items: CarouselItemProps[];
visible: boolean;
}
interface CarouselPageState {}
export default class CarouselPage extends Component<CarouselPageProps, CarouselPageState> {
constructor(props: CarouselPageProps) {
super(props);
this.state = {};
}
export default class CarouselPage extends Component<
CarouselPageProps,
CarouselPageState
> {
constructor(props: CarouselPageProps) {
super(props);
this.state = {};
}
public render() {
return <div className="carousel-page" style={this.props.visible ? {} : {display: "none"}}>{
this.props.items.map(n=>(
<CarouselItem {...n}></CarouselItem>
))
}</div>;
}
public render() {
return (
<div
className="carousel-page"
style={this.props.visible ? {} : { display: "none" }}
>
{this.props.items.map((n) => (
<CarouselItem {...n}></CarouselItem>
))}
</div>
);
}
}

View file

@ -1,26 +1,37 @@
import * as React from "react";
import {Component} from "react";
import { Component } from "react";
interface DefaultSectionProps {
title: string;
paragraphs?: string[];
className?: string;
title: string;
paragraphs?: string[];
className?: string;
}
interface DefaultSectionState {}
export default class DefaultSection extends Component<DefaultSectionProps, DefaultSectionState> {
constructor(props: DefaultSectionProps) {
super(props);
this.state = {};
}
export default class DefaultSection extends Component<
DefaultSectionProps,
DefaultSectionState
> {
constructor(props: DefaultSectionProps) {
super(props);
this.state = {};
}
public render() {
return <div className={`default-section ${this.props.className ? this.props.className : ""}`}>
<div className="section-title">{this.props.title}</div>
{(this.props.paragraphs ? this.props.paragraphs : []).map(n=>(
<p>{n}</p>
))}
{this.props.children}
</div>;
}
public render() {
return (
<div
className={`default-section ${
this.props.className ? this.props.className : ""
}`}
>
<div className="section-title">{this.props.title}</div>
{(this.props.paragraphs ? this.props.paragraphs : []).map(
(n) => (
<p>{n}</p>
)
)}
{this.props.children}
</div>
);
}
}

View file

@ -1,25 +1,31 @@
import * as React from "react";
import {Component} from "react";
import { Component } from "react";
import SocialCard from "./SocialCard";
import {EMAIL_WHITE, SOCIALS_WHITE} from "../Config";
import { EMAIL_WHITE, SOCIALS_WHITE } from "../Config";
interface FooterProps {}
interface FooterState {}
export default class Footer extends Component<FooterProps, FooterState> {
constructor(props: FooterProps) {
super(props);
this.state = {};
}
constructor(props: FooterProps) {
super(props);
this.state = {};
}
public render() {
return <div className="footer">
<img src="img/logo_white.svg"></img>
<div className="footer-scls">{
[...EMAIL_WHITE, ...SOCIALS_WHITE].map(n => (
<SocialCard url={n.url} image={n.icon} message={n.message}></SocialCard>
))
}</div>
</div>;
}
public render() {
return (
<div className="footer">
<img src="img/logo_white.svg"></img>
<div className="footer-scls">
{[...EMAIL_WHITE, ...SOCIALS_WHITE].map((n) => (
<SocialCard
url={n.url}
image={n.icon}
message={n.message}
></SocialCard>
))}
</div>
</div>
);
}
}

View file

@ -1,24 +1,36 @@
import * as React from "react";
import {Component} from "react";
import { Component } from "react";
interface InvolveBoxProps {
boxTitle: string;
image: string;
description: string;
boxTitle: string;
image: string;
description: string;
}
interface InvolveBoxState {}
export default class InvolveBox extends Component<InvolveBoxProps, InvolveBoxState> {
constructor(props: InvolveBoxProps) {
super(props);
this.state = {};
}
export default class InvolveBox extends Component<
InvolveBoxProps,
InvolveBoxState
> {
constructor(props: InvolveBoxProps) {
super(props);
this.state = {};
}
public render() {
return <div className="involve-card">
<a className="involve-title" href={"/"+this.props.boxTitle.toLowerCase()}>{this.props.boxTitle}</a>
<img src={this.props.image}></img>
<div className="involve-description">{this.props.description}</div>
</div>;
}
public render() {
return (
<div className="involve-card">
<a
className="involve-title"
href={"/" + this.props.boxTitle.toLowerCase()}
>
{this.props.boxTitle}
</a>
<img src={this.props.image}></img>
<div className="involve-description">
{this.props.description}
</div>
</div>
);
}
}

View file

@ -1,23 +1,28 @@
import * as React from "react";
import {Component} from "react";
import { Component } from "react";
interface SocialCardProps {
url: string;
image: string;
message: string;
url: string;
image: string;
message: string;
}
interface SocialCardState {}
export default class SocialCard extends Component<SocialCardProps, SocialCardState> {
constructor(props: SocialCardProps) {
super(props);
this.state = {};
}
export default class SocialCard extends Component<
SocialCardProps,
SocialCardState
> {
constructor(props: SocialCardProps) {
super(props);
this.state = {};
}
public render() {
return <a href={this.props.url} className="social-card">
<img src={this.props.image}></img>
<div className="social-message">{this.props.message}</div>
</a>;
}
public render() {
return (
<a href={this.props.url} className="social-card">
<img src={this.props.image}></img>
<div className="social-message">{this.props.message}</div>
</a>
);
}
}

View file

@ -1,46 +1,56 @@
import * as React from "react";
import {Component} from "react";
import {SOCIALS_WHITE} from "../Config";
import { Component } from "react";
import { SOCIALS_WHITE } from "../Config";
interface SplashProps {
cta: string;
backgrounds: string[];
delay: number;
cta: string;
backgrounds: string[];
delay: number;
}
interface SplashState {
progress: number;
progress: number;
}
export default class Splash extends Component<SplashProps, SplashState> {
private interval: number;
constructor(props: SplashProps) {
super(props);
this.state = {
progress: 0
};
this.interval = setInterval(this.changeImage.bind(this), this.props.delay) as any as number;
}
private interval: number;
constructor(props: SplashProps) {
super(props);
this.state = {
progress: 0,
};
this.interval = setInterval(
this.changeImage.bind(this),
this.props.delay
) as any as number;
}
private changeImage(): void {
if (this.state.progress < this.props.backgrounds.length - 1) {
this.setState({progress: this.state.progress + 1});
} else {
this.setState({progress: 0});
}
}
private changeImage(): void {
if (this.state.progress < this.props.backgrounds.length - 1) {
this.setState({ progress: this.state.progress + 1 });
} else {
this.setState({ progress: 0 });
}
}
public render() {
return <div className="splash" style={{
backgroundImage: `url("${this.props.backgrounds[this.state.progress]}")`
}}>
<div className="call-to-action">{this.props.cta}</div>
<div className="splash-socials">{
SOCIALS_WHITE.map(n=>(
<a href={n.url} key={n.icon}>
<img src={n.icon}></img>
</a>
))
}</div>
</div>;
}
public render() {
return (
<div
className="splash"
style={{
backgroundImage: `url("${
this.props.backgrounds[this.state.progress]
}")`,
}}
>
<div className="call-to-action">{this.props.cta}</div>
<div className="splash-socials">
{SOCIALS_WHITE.map((n) => (
<a href={n.url} key={n.icon}>
<img src={n.icon}></img>
</a>
))}
</div>
</div>
);
}
}

View file

@ -1,59 +1,82 @@
import * as React from "react";
import {Component} from "react";
import { Component } from "react";
interface TopBarProps {
links: {
name: string;
url: string;
}[];
links: {
name: string;
url: string;
}[];
}
interface TopBarState {
showBurger: boolean;
menuVisible: boolean;
showBurger: boolean;
menuVisible: boolean;
}
export default class TopBar extends Component<TopBarProps, TopBarState> {
private static HAMBURGER_POINT = 1290;
constructor(props: TopBarProps) {
super(props);
this.state = {
showBurger: this.shouldShowBurger(),
menuVisible: false
};
window.addEventListener("resize", this.checkResize.bind(this));
}
private toggleMenu() {
this.setState({menuVisible: !this.state.menuVisible});
}
private checkResize() {
this.setState({showBurger: this.shouldShowBurger()});
}
private shouldShowBurger(): boolean {
return window.innerWidth < TopBar.HAMBURGER_POINT;
}
private static HAMBURGER_POINT = 1290;
constructor(props: TopBarProps) {
super(props);
this.state = {
showBurger: this.shouldShowBurger(),
menuVisible: false,
};
window.addEventListener("resize", this.checkResize.bind(this));
}
private toggleMenu() {
this.setState({ menuVisible: !this.state.menuVisible });
}
private checkResize() {
this.setState({ showBurger: this.shouldShowBurger() });
}
private shouldShowBurger(): boolean {
return window.innerWidth < TopBar.HAMBURGER_POINT;
}
public render() {
return <div className={`topbar${
this.state.showBurger ? " burger-bar" : ""
}`}>
<div className="img-cont">
<a href="/"><img src="img/logo.svg"></img></a>
<div className="burger" style={{
display: this.state.showBurger ? "initial" : "none"
}} onClick={this.toggleMenu.bind(this)} role="menubar"></div>
</div>
<div className="links">
<div className={`link-items${
this.state.showBurger ? " burger-time" : ""
}`} style={{display: this.state.showBurger ?
(this.state.menuVisible ? "flex" : "none") :
"initial"}}>{
this.props.links.map(l=>{
return <a className="navlink" href={l.url}>{l.name}</a>
})
}</div>
</div>
</div>;
}
public render() {
return (
<div
className={`topbar${
this.state.showBurger ? " burger-bar" : ""
}`}
>
<div className="img-cont">
<a href="/">
<img src="img/logo.svg"></img>
</a>
<div
className="burger"
style={{
display: this.state.showBurger ? "initial" : "none",
}}
onClick={this.toggleMenu.bind(this)}
role="menubar"
>
</div>
</div>
<div className="links">
<div
className={`link-items${
this.state.showBurger ? " burger-time" : ""
}`}
style={{
display: this.state.showBurger
? this.state.menuVisible
? "flex"
: "none"
: "initial",
}}
>
{this.props.links.map((l) => {
return (
<a className="navlink" href={l.url}>
{l.name}
</a>
);
})}
</div>
</div>
</div>
);
}
}

View file

@ -33,6 +33,7 @@
url('../fonts/roboto-v29-latin-700.woff') format('woff');
/* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* montserrat-regular - latin */
@font-face {
font-family: 'Montserrat';

View file

@ -1,29 +1,34 @@
/* Color vars should go here */
/* Global CSS Properties */
:root {
--main: #00629B;
--accent: #FFCD00;
--secondary: white;
--dark: #2A1A4E;
--main: #00629b;
--accent: #ffcd00;
--secondary: #ffffff;
--dark: #2a1a4e;
}
* {
scrollbar-width: auto;
scrollbar-color: var(--main) transparent;
}
*::-webkit-scrollbar {
width: 0.6em;
height: 0.6em;
}
*::-webkit-scrollbar-track {
background: var(--main);
}
*::-webkit-scrollbar-thumb {
background-color: var(--secondary);
}
html {
font-family: "Roboto", sans-serif;
font-weight: bold;
}
body {
margin: 0;
}
@ -41,6 +46,7 @@ a:hover {
display: flex;
flex-direction: column;
}
.topbar {
display: flex;
flex-direction: row;
@ -50,20 +56,24 @@ a:hover {
padding: 0.5em;
align-items: center;
}
.topbar.burger-bar {
flex-direction: column;
position: relative;
}
.topbar.burger-bar > .img-cont {
.topbar.burger-bar>.img-cont {
align-self: flex-start;
display: flex;
justify-content: space-between;
width: 100%;
align-items: center;
}
.img-cont img {
width: 22em;
}
.burger {
font-size: 2.8em;
font-weight: normal;
@ -71,26 +81,31 @@ a:hover {
cursor: pointer;
margin-right: 0.3em;
}
.link-items.burger-time {
flex-direction: column;
position: absolute;
background-color: var(--secondary);
width: 10em;
box-shadow: rgba(0,0,0,0.3) 4px 4px 3px 0px;
box-shadow: rgba(0, 0, 0, 0.3) 4px 4px 3px 0px;
right: 0;
margin-top: 0.36em;
}
.link-items.burger-time > a {
.link-items.burger-time>a {
margin: 0;
padding: 0.4em 0.75em;
}
.link-items.burger-time > a:hover {
background-color: rgba(0,0,0,0.1);
.link-items.burger-time>a:hover {
background-color: rgba(0, 0, 0, 0.1);
}
.navlink {
font-size: 1.1em;
margin: 0.75em;
}
.splash {
background-color: var(--main);
background-blend-mode: overlay;
@ -104,6 +119,7 @@ a:hover {
background-repeat: no-repeat;
background-size: cover;
}
.call-to-action {
font-size: 3.2em;
width: 11em;
@ -111,16 +127,20 @@ a:hover {
text-align: center;
font-family: "Montserrat";
}
.splash-socials {
margin-top: 2em;
}
.splash-socials > a {
.splash-socials>a {
margin-left: 1.5em;
margin-right: 1.5em;
}
.splash-socials img {
width: 4.7em;
}
.default-section {
display: flex;
align-items: center;
@ -131,28 +151,34 @@ a:hover {
padding-top: 2em;
padding-bottom: 2em;
}
.section-title {
font-size: 3em;
margin-top: 0.8em;
margin-bottom: 0.8em;
}
.default-section > p, .project-space > p {
.default-section>p,
.project-space>p {
font-size: 1.8em;
width: 22em;
color: var(--dark);
text-align: center;
}
.project-space > p {
.project-space>p {
color: var(--secondary);
}
.default-section > p:first-child {
.default-section>p:first-child {
margin-top: 0;
}
.project-space {
border-radius: 50% 50% 0 0 / 4rem;
background-color: var(--main);
background-blend-mode: overlay;
background-image: url("../img/backgrounds/ps.png");
background-image: url("../img/backgrounds/ps.webp");
height: 40em;
display: flex;
justify-content: center;
@ -161,32 +187,39 @@ a:hover {
background-repeat: no-repeat;
background-size: cover;
}
.project-space a {
color: var(--accent);
}
.ps-title {
font-size: 3em;
color: white;
margin-bottom: 1em;
}
.visit-us {
font-size: 2em;
margin-bottom: 2em;
}
.ex-link {
color: var(--accent);
font-size: 2em;
margin-bottom: 2em;
}
.involved {
border-radius: 50% 50% 0 0 / 4rem;
transform: translateY(-4rem);
}
.cards {
display: flex;
justify-content: space-around;
flex-wrap: wrap;
}
.involve-card {
padding: 1.75em 2.5em;
border: var(--main) solid 0.37em;
@ -198,32 +231,41 @@ a:hover {
flex-direction: column;
margin: 1em;
}
.involve-title {
font-size: 3em;
color: var(--main);
margin-bottom: 0.5em;
}
.involve-card > img {
.involve-card>img {
width: 20em;
margin-bottom: 1em;
border-radius: 0.2em;
}
.links {
font-size: 1.5em;
}
.social-card {
display: flex;
align-items: center;
padding: 1em;
width: 25em;
}
.social-card > img {
.social-card>img {
width: 4.7em;
}
.social-message {
font-size: 2em;
padding: 0.5em;
}
.join-scls, .footer-scls {
.join-scls,
.footer-scls {
display: flex;
flex-direction: row;
max-width: 55em;
@ -231,6 +273,7 @@ a:hover {
flex-wrap: wrap;
margin-left: 5em;
}
.carousel-item {
display: flex;
flex-direction: column;
@ -240,45 +283,58 @@ a:hover {
border-radius: 1em;
margin: 1em;
}
.carousel-item > img {
.carousel-item>img {
width: 18.7em;
border-radius: 0.2em;
}
.carousel-name {
font-size: 2em;
}
.carousel-pos {
font-style: italic;
font-weight: normal;
font-size: 1.25em;
}
.carousel-email {
margin-top: 0.5em;
}
.carousel-page {
display: flex;
flex-wrap: wrap;
flex-wrap: wrap;
justify-content: center;
}
.carousel {
display: flex;
align-items: center;
justify-content: center;
}
.carousel-left {
margin-left: 2em;
transform: scaleX(-1);
}
.carousel-right, .carousel-left {
.carousel-right,
.carousel-left {
cursor: pointer;
width: 4em;
}
.carousel-right {
margin-right: 2em;
}
.contact {
border-radius: 0 0 50% 50% / 4rem;
transform: translateY(4rem);
}
.footer {
background-color: var(--main);
padding-top: 7em;
@ -288,18 +344,22 @@ a:hover {
padding-left: 2em;
padding-right: 2em;
}
.footer a {
color: white;
}
.footer-scls {
max-width: 30em;
font-size: 0.6em;
margin-bottom: 2em;
}
.footer-scls img {
width: 4.7em;
}
.footer > img {
.footer>img {
width: 30em;
}
@ -309,27 +369,33 @@ a:hover {
font-size: 0.85em;
}
}
@media screen and (max-width: 650px) {
html {
font-size: 0.8em;
}
.footer > img{
.footer>img {
width: 15em;
}
}
@media screen and (max-width: 540px) {
html {
font-size: 0.6em;
}
.footer>img {
display: none;
}
}
@media screen and (max-width: 420px) {
html {
font-size: 0.5em;
}
}
@media screen and (max-width: 320px) {
html {
font-size: 0.45em;
@ -337,9 +403,9 @@ a:hover {
}
#cal {
border:solid 1px #777;
border: solid 1px #777;
width: 800px;
height: 600px;
height: 600px;
}
#calendar {
@ -354,6 +420,6 @@ a:hover {
iframe {
width: 80vw;
height: 600px;
max-width: 1000px;
height: 600px;
max-width: 1000px;
}

View file

@ -1,7 +1,7 @@
import * as ReactDom from "react-dom";
import * as React from "react";
import TopBar from "./components/TopBar";
import {ACTIVE_PAGES, SOCIALS, EMAIL, OFFICERS} from "./Config";
import { ACTIVE_PAGES, SOCIALS, EMAIL, OFFICERS } from "./Config";
import Splash from "./components/Splash";
import DefaultSection from "./components/DefaultSection";
import InvolveBox from "./components/InvolveBox";
@ -13,38 +13,59 @@ interface MainProps {}
interface MainState {}
class Main extends React.Component<MainProps, MainState> {
constructor(props: MainProps) {
super(props);
this.state = {};
}
public render() {
return <>
<TopBar links={ACTIVE_PAGES}></TopBar>
<Splash cta="Come out to our events!" delay={2000} backgrounds={["img/backgrounds/fa21social.png"]}></Splash>
constructor(props: MainProps) {
super(props);
this.state = {};
}
public render() {
return (
<>
<TopBar links={ACTIVE_PAGES}></TopBar>
<Splash
cta="Come out to our events!"
delay={2000}
backgrounds={["img/backgrounds/fa21social.webp"]}
></Splash>
<DefaultSection title="Events">
<iframe src="https://calendar.google.com/calendar/embed?src=666sh64sku5n29qv2a2f4598jc%40group.calendar.google.com&ctz=America%2FLos_Angeles" frameBorder="0" scrolling="no"></iframe>
</DefaultSection>
<DefaultSection title="Events">
<iframe
src="https://calendar.google.com/calendar/embed?src=666sh64sku5n29qv2a2f4598jc%40group.calendar.google.com&ctz=America%2FLos_Angeles"
frameBorder="0"
scrolling="no"
></iframe>
</DefaultSection>
<DefaultSection title="Open Access Hours">
<iframe src="https://calendar.google.com/calendar/embed?src=c_gr3iim9ae4dv9784qkf8meb40c%40group.calendar.google.com&ctz=America%2FLos_Angeles" frameBorder="0" scrolling="no"></iframe>
</DefaultSection>
<DefaultSection title="Open Access Hours">
<iframe
src="https://calendar.google.com/calendar/embed?src=c_gr3iim9ae4dv9784qkf8meb40c%40group.calendar.google.com&ctz=America%2FLos_Angeles"
frameBorder="0"
scrolling="no"
></iframe>
</DefaultSection>
<div id="contact-us">
<DefaultSection className="contact" title="Have questions? Contact us!">
<div className="join-scls">{
[...EMAIL, ...SOCIALS].map(n => (
<SocialCard url={n.url} image={n.icon} message={n.message}></SocialCard>
))
}</div>
</DefaultSection>
</div>
<div id="contact-us">
<DefaultSection
className="contact"
title="Have questions? Contact us!"
>
<div className="join-scls">
{[...EMAIL, ...SOCIALS].map((n) => (
<SocialCard
url={n.url}
image={n.icon}
message={n.message}
></SocialCard>
))}
</div>
</DefaultSection>
</div>
<Footer></Footer>
</>;
}
<Footer></Footer>
</>
);
}
}
ReactDom.render(<Main></Main>, document.getElementById("root"));
export default {};
export default {};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 915 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 449 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 771 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 896 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 897 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 480 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 356 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View file

@ -1,7 +1,7 @@
import * as ReactDom from "react-dom";
import * as React from "react";
import TopBar from "./components/TopBar";
import {ACTIVE_PAGES, SOCIALS, EMAIL, OFFICERS} from "./Config";
import { ACTIVE_PAGES, SOCIALS, EMAIL, OFFICERS } from "./Config";
import Splash from "./components/Splash";
import DefaultSection from "./components/DefaultSection";
import InvolveBox from "./components/InvolveBox";
@ -13,50 +13,97 @@ interface MainProps {}
interface MainState {}
class Main extends React.Component<MainProps, MainState> {
constructor(props: MainProps) {
super(props);
this.state = {};
}
public render() {
return <>
<TopBar links={ACTIVE_PAGES}></TopBar>
<Splash cta="Join the 2nd largest IEEE student branch in the US!" delay={2000} backgrounds={["img/backgrounds/fa21qp.png"]}></Splash>
<div id="about-us">
<DefaultSection title="We are..." paragraphs={[
"A diverse engineering community seeking to empower students through hands-on projects, networking opportunities, and social events. Bonus points on having an open-access project studio!",
"The Institute of Electrical and Electronics Engineers (IEEE) UC San Diego student branch is the second largest student chapter in the worlds largest professional organization. On the student level, we provide members with a plethora of ways to get involved!"
]}></DefaultSection>
</div>
<div className="project-space">
<div className="ps-title">Join us at the Project Space!</div>
<p>The <a href="https://www.google.com/maps/@32.8817126,-117.2350998,59m/">IEEE Project Space</a> is an open-access, collaborative space where students can do homework or get access to basic electronic tools such as soldering stations, breadboard components, and Arduino and Raspberry PI parts!</p>
<a className="visit-us" href="https://www.google.com/maps/@32.8817126,-117.2350998,59m/">Come visit at EBU1-4710!</a>
</div>
<DefaultSection className={"involved"} title="How else can I get involved?">
<div className="cards">
<InvolveBox boxTitle="Events" image="img/backgrounds/fa21social.png" description="Meet fellow IEEE members!"></InvolveBox>
<InvolveBox boxTitle="Projects" image="img/backgrounds/robofest.png" description="Learn new skills!"></InvolveBox>
<InvolveBox boxTitle="Committees" image="img/backgrounds/gbm.png" description="Build our amazing community!"></InvolveBox>
</div>
</DefaultSection>
<div id="contact-us">
<DefaultSection title="Have questions? Contact us!">
<div className="join-scls">{
[...EMAIL, ...SOCIALS].map(n => (
<SocialCard url={n.url} image={n.icon} message={n.message}></SocialCard>
))
}</div>
</DefaultSection>
<DefaultSection className="contact" title="Or... Contact one of our staff!">
<Carousel items={OFFICERS} itemsPerPage={6}></Carousel>
</DefaultSection>
</div>
<Footer></Footer>
</>;
}
constructor(props: MainProps) {
super(props);
this.state = {};
}
public render() {
return (
<>
<TopBar links={ACTIVE_PAGES}></TopBar>
<Splash
cta="Join the 2nd largest IEEE student branch in the US!"
delay={2000}
backgrounds={["img/backgrounds/fa21qp.webp"]}
></Splash>
<div id="about-us">
<DefaultSection
title="We are..."
paragraphs={[
"A diverse engineering community seeking to empower students through hands-on projects, networking opportunities, and social events. Bonus points on having an open-access project studio!",
"The Institute of Electrical and Electronics Engineers (IEEE) UC San Diego student branch is the second largest student chapter in the worlds largest professional organization. On the student level, we provide members with a plethora of ways to get involved!",
]}
></DefaultSection>
</div>
<div className="project-space">
<div className="ps-title">
Join us at the Project Space!
</div>
<p>
The{" "}
<a href="https://www.google.com/maps/@32.8817126,-117.2350998,59m/">
IEEE Project Space
</a>{" "}
is an open-access, collaborative space where students
can do homework or get access to basic electronic tools
such as soldering stations, breadboard components, and
Arduino and Raspberry PI parts!
</p>
<a
className="visit-us"
href="https://www.google.com/maps/@32.8817126,-117.2350998,59m/"
>
Come visit at EBU1-4710!
</a>
</div>
<DefaultSection
className={"involved"}
title="How else can I get involved?"
>
<div className="cards">
<InvolveBox
boxTitle="Events"
image="img/backgrounds/fa21social.webp"
description="Meet fellow IEEE members!"
></InvolveBox>
<InvolveBox
boxTitle="Projects"
image="img/backgrounds/robofest.webp"
description="Learn new skills!"
></InvolveBox>
<InvolveBox
boxTitle="Committees"
image="img/backgrounds/gbm.webp"
description="Build our amazing community!"
></InvolveBox>
</div>
</DefaultSection>
<div id="contact-us">
<DefaultSection title="Have questions? Contact us!">
<div className="join-scls">
{[...EMAIL, ...SOCIALS].map((n) => (
<SocialCard
url={n.url}
image={n.icon}
message={n.message}
></SocialCard>
))}
</div>
</DefaultSection>
<DefaultSection
className="contact"
title="Or... Contact one of our staff!"
>
<Carousel items={OFFICERS} itemsPerPage={6}></Carousel>
</DefaultSection>
</div>
<Footer></Footer>
</>
);
}
}
ReactDom.render(<Main></Main>, document.getElementById("root"));
export default {};
export default {};

View file

@ -1,7 +1,7 @@
import * as ReactDom from "react-dom";
import * as React from "react";
import TopBar from "./components/TopBar";
import {ACTIVE_PAGES, SOCIALS, EMAIL, OFFICERS} from "./Config";
import { ACTIVE_PAGES, SOCIALS, EMAIL, OFFICERS } from "./Config";
import Splash from "./components/Splash";
import DefaultSection from "./components/DefaultSection";
import InvolveBox from "./components/InvolveBox";
@ -13,48 +13,86 @@ interface MainProps {}
interface MainState {}
class Main extends React.Component<MainProps, MainState> {
constructor(props: MainProps) {
super(props);
this.state = {};
}
public render() {
return <>
<TopBar links={ACTIVE_PAGES}></TopBar>
<Splash cta="Gain hands-on experience to make your resume stand out! No experience required!" delay={2000} backgrounds={["img/backgrounds/robocar.png"]}></Splash>
<DefaultSection title="RoboCup" paragraphs={[
"\"RoboCup is an international scientific initiative with the goal to advance the state of the art of intelligent robots. When established in 1997, the original mission was to field a team of robots capable of winning against the human soccer World Cup champions by 2050.\""
,"IEEE hosts Robocup Soccer, an annual project where teams develop six robots to compete with other teams during Robofest. Join this hands-on project to explore computer vision, mechanical design, and microcontroller development!"
]}>
<a className='ex-link' href='https://www.robocup.org/'>RoboCup website</a>
</DefaultSection>
<DefaultSection title="Quarterly Projects" paragraphs={[
"Getting started on hardware development or want to make your own project? IEEE's Quarterly Projects aims to provide students with project experience in a span of 10 weeks. During QP, students will acquire skills used in the industry such as C++ and the prototyping process with the assistance of our mentors."
]}>
<a className='ex-link' href='https://forms.gle/eW6e1i3vWCdBj7Vn6'>Apply here</a>
</DefaultSection>
<DefaultSection className={"past-proj"} title="Past Projects">
<div className="cards">
<InvolveBox boxTitle="" image="img/backgrounds/robocar.png" description="'22 Robocar Competition"></InvolveBox>
<InvolveBox boxTitle="" image="img/backgrounds/micromouse.png" description="'22 Micromouse Competition"></InvolveBox>
<InvolveBox boxTitle="" image="img/backgrounds/sp22qp.png" description="'22 Spring QP Showcase"></InvolveBox>
</div>
</DefaultSection>
constructor(props: MainProps) {
super(props);
this.state = {};
}
public render() {
return (
<>
<TopBar links={ACTIVE_PAGES}></TopBar>
<Splash
cta="Gain hands-on experience to make your resume stand out! No experience required!"
delay={2000}
backgrounds={["img/backgrounds/robocar.webp"]}
></Splash>
<DefaultSection
title="RoboCup"
paragraphs={[
'"RoboCup is an international scientific initiative with the goal to advance the state of the art of intelligent robots. When established in 1997, the original mission was to field a team of robots capable of winning against the human soccer World Cup champions by 2050."',
"IEEE hosts Robocup Soccer, an annual project where teams develop six robots to compete with other teams during Robofest. Join this hands-on project to explore computer vision, mechanical design, and microcontroller development!",
]}
>
<a className="ex-link" href="https://www.robocup.org/">
RoboCup website
</a>
</DefaultSection>
<DefaultSection
title="Quarterly Projects"
paragraphs={[
"Getting started on hardware development or want to make your own project? IEEE's Quarterly Projects aims to provide students with project experience in a span of 10 weeks. During QP, students will acquire skills used in the industry such as C++ and the prototyping process with the assistance of our mentors.",
]}
>
<a
className="ex-link"
href="https://forms.gle/eW6e1i3vWCdBj7Vn6"
>
Apply here
</a>
</DefaultSection>
<DefaultSection className={"past-proj"} title="Past Projects">
<div className="cards">
<InvolveBox
boxTitle=""
image="img/backgrounds/robocar.webp"
description="'22 Robocar Competition"
></InvolveBox>
<InvolveBox
boxTitle=""
image="img/backgrounds/micromouse.webp"
description="'22 Micromouse Competition"
></InvolveBox>
<InvolveBox
boxTitle=""
image="img/backgrounds/sp22qp.webp"
description="'22 Spring QP Showcase"
></InvolveBox>
</div>
</DefaultSection>
<div id="contact-us">
<DefaultSection className="contact" title="Have questions? Contact us!">
<div className="join-scls">{
[...EMAIL, ...SOCIALS].map(n => (
<SocialCard url={n.url} image={n.icon} message={n.message}></SocialCard>
))
}</div>
</DefaultSection>
</div>
<div id="contact-us">
<DefaultSection
className="contact"
title="Have questions? Contact us!"
>
<div className="join-scls">
{[...EMAIL, ...SOCIALS].map((n) => (
<SocialCard
url={n.url}
image={n.icon}
message={n.message}
></SocialCard>
))}
</div>
</DefaultSection>
</div>
<Footer></Footer>
</>;
}
<Footer></Footer>
</>
);
}
}
ReactDom.render(<Main></Main>, document.getElementById("root"));
export default {};
export default {};

View file

@ -1,5 +1,6 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>$TITLE</title>
<link rel="stylesheet" type="text/css" href="css/fonts.css" media="screen">
@ -9,8 +10,10 @@
<meta name="description" content="$DESCRIPTION">
<meta name="theme-color" content="$THEMECOLOR">
</head>
<body>
<div id="root"></div>
<script type="text/javascript" src="$JSFILE"></script>
<div id="root"></div>
<script type="text/javascript" src="$JSFILE"></script>
</body>
</html>

View file

@ -1,73 +1,78 @@
const path = require("path");
const CopyPlugin = require("copy-webpack-plugin");
const LicensePlugin = require("license-webpack-plugin").LicenseWebpackPlugin;
const TerserPlugin = require('terser-webpack-plugin');
const TerserPlugin = require("terser-webpack-plugin");
const fs = require("fs");
module.exports = {
entry: loadEntries("./src/public"),
output: {
path: path.resolve(__dirname, "build/public"),
filename: "./js/[name].js"
},
module: {
rules: [{
test: /\.(tsx|ts)$/,
use: "ts-loader"
},
{
test: /\.(js)$/,
use: "babel-loader"
},
{
test: /\.css$/,
use: ["style-loader", "css-loader"]
}
]
},
resolve: {
extensions: [".tsx", ".ts", ".js"],
},
mode: "production",
plugins: [
new CopyPlugin({
patterns: [{
from: "./src/public",
to: ".",
globOptions: {
ignore: ["**/*.tsx", "**/*.ts"]
}
}
],
}),
new LicensePlugin({
outputFilename: 'third-party-notice.txt'
})
],
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
output: {
comments: false,
},
},
extractComments: false,
}),
],
},
//devtool: "source-map"
entry: loadEntries("./src/public"),
output: {
path: path.resolve(__dirname, "build/public"),
filename: "./js/[name].js",
},
module: {
rules: [
{
test: /\.(tsx|ts)$/,
use: "ts-loader",
},
{
test: /\.(js)$/,
use: "babel-loader",
},
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
],
},
resolve: {
extensions: [".tsx", ".ts", ".js"],
},
mode: "production",
plugins: [
new CopyPlugin({
patterns: [
{
from: "./src/public",
to: ".",
globOptions: {
ignore: ["**/*.tsx", "**/*.ts"],
},
},
],
}),
new LicensePlugin({
outputFilename: "third-party-notice.txt",
}),
],
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
output: {
comments: false,
},
},
extractComments: false,
}),
],
},
//devtool: "source-map"
devServer: {
static: "./build/public",
},
};
function loadEntries(dir) {
let files = fs.readdirSync(path.join(__dirname, dir));
let entries = {};
files.forEach(file => {
let name = file.match(/^(.*)\.tsx$/);
if (name) {
entries[name[1]] = path.join(__dirname, dir, file);
}
});
return entries;
}
let files = fs.readdirSync(path.join(__dirname, dir));
let entries = {};
files.forEach((file) => {
let name = file.match(/^(.*)\.tsx$/);
if (name) {
entries[name[1]] = path.join(__dirname, dir, file);
}
});
return entries;
}