Z-teknologsektionen's website ztek.se
This project is used to build Z-Teknologsektionen's website. It was bootstrapped with create-t3-app. The application is a fullstack application built with the following technologies:
and deployed with Vercel. The website contains information about the student division, life as a student at Z, the board, the different committees and the events that are being held. It is managed by Webbgruppen which also are responsible for the development of the website.
This guide is created to help new member of Webbgruppen to get started with the project and to understand how the project is built and how it works. The guide is divided into different sections where each section explains a different part of the project. It assumes that you have no prior knowledge of the different technologies used in the project and is written to be easy to understand. If you have any questions or need help with anything, feel free to reach out to the other members of Webbgruppen.
Before you can run this project, you need to have the following installed:
-
Node.js: Next.js requires Node.js to run. You can download it from the official Node.js website. The LTS (Long Term Support) version is recommended.
-
npm: npm is the package manager for Node.js and is used to install Next.js and other dependencies. npm is included with Node.js, so you don't need to install it separately.
-
Git: You will need Git to clone the project's repository. You can download it from the official Git website.
After installing the prerequisites, you can clone the project's repository with the following command:
git clone https://github.com/Z-Teknologsektionen/ztek.git
To be able to access the github repository, you need to be a member of the organization Z-Teknologsektionen. If you are not a member, you can request access from Ztyret.
Finally, install the project's dependencies with the following command:
npm install or npm i
and run the project with
npm run dev
The application might return an error when connecting to the database because no valid environment file is present. Look at .env.example
and create a copy of it. Rename it to .env
and fill it with the correct values. For more information, see this link.
DATABASE_URL=""
#Next Auth
NEXTAUTH_URL=''
NEXTAUTH_SECRET=''
#Next Auth Google Login
GOOGLE_CLIENT_ID=''
GOOGLE_CLIENT_SECRET=''
This is what the current .env
file looks for this project. Reach out to other members to get the correct values.
Now you should be able to run the project and connect to all relevant databases. If the code still throws an error, try restarting the ESLint server by pressing ctrl + shift + p
and typing ESLint: Restart ESLint Server
.
When checking out a branch, always run npm install
to make sure you have all the dependencies needed for the project. If you are having issues with the project, try running npm run lint
and npm run typecheck
to see if there are any errors in the code. If you are still having issues, reach out to the other members of Webbgruppen.
Sometimes prisma does its own thing and throws errors. If you are having issues with prisma, try running npx prisma generate
to regenerate the prisma files.
A typical Next.js project has a specific file structure. Here's a brief overview of the main directories and files you'll find in a Next.js project and their roles:
-
src/pages/
: This directory contains all the page components. Each file corresponds to a route based on its name. For example,pages/about.tsx
is mapped towww.ztek.se/about
. The special filepages/_app.tsx
is used to initialize pages, andpages/_document.tsx
is used for server-side rendering. -
public/
: The public directory is used to serve static files. Files within this directory are accessible atwww.ztek.se/filename
. Usually images and pdf-documents are placed here. -
src/styles/
: This directory is often used for global styles. -
src/components/
: All custom components are placed in here. These components can be reused across different pages. -
node_modules/
: This directory is where npm packages are stored after you runnpm install
. -
package.json
: This file contains metadata about the project and its dependencies. -
package-lock.json
: These files lock the versions of the dependencies to ensure consistency across environments. -
.next/
: This is the output directory of the Next.js build process. It's created when you runnext build
. -
next.config.js
: This is the configuration file for Next.js. You can adjust the behavior of your Next.js app by modifying this file. -
.env
: This file is used to define environment variables. This is needed to keep sensitive information like API keys and database credentials out of your codebase.
The src/pages/
directory is the heart of a Next.js application. Each file in this directory corresponds to a route in your application. These files import and use components from the src/components/
directory or any other directory you choose to structure your components in.
src
├── pages
│ ├── active
│ ├── api
│ ├── student
│ ├── student-division
....
These are the base routes for the project, each corresponding to a part of the website. The different routes can be accessed from the Navbar on the website. The active
route is used to administer the different parts of the site and you need to be logged in to access this route.
The api
route is used to setup the tRPC and NextAuth api.
The student
route is used to display information only relevant to students and not related to the student division. Here information about applying for Z and other student related information is displayed.
The student-division
route is used to display the different student division committees and how to apply for them.
The page rendered when you visit ztek.se/student-division
is rendered from the index.tsx
file placed in the student-division
folder. This enables the application to have a base route and subroutes. Within the folder, there is also a file named zaloonen.tsx
which is displayed at the route ztek.se/student-division/zaloonen
. So index.tsx
in any folder corresponds to the folder name while another filename corresponds to the filename.
Sometimes we don't know the route, as it might depend on a name defined in some database. In this project this happens for committees at src/pages/student-division/committees/[slug]
. Here the brackets indicate that the route is dynamic and the slug of the committee is used to display a specific commitee. Each committee has the same layout, but the text and images are changed based on the api call which uses the slug to get data.
The different routes are mainly built with different components such as the <SectionWrapper>
component. This component is used to wrap the different sections of the website and is used to keep the same layout across the different routes. Looking at the component, we can see that it has different margins for different screensizes, making the website nice both for computers and phones. This component should be wrapped around every different page.
import {
type DetailedHTMLProps,
type FC,
type HTMLAttributes,
type PropsWithChildren,
} from "react";
import { cn } from "~/utils/utils";
interface ISectionWrapper
extends DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement> {
a?: boolean;
}
const SectionWrapper: FC<PropsWithChildren<ISectionWrapper>> = ({
children,
className = "",
...rest
}) => {
return (
<section
className={cn(
"mx-auto max-w-7xl space-y-8 px-4 py-16 md:px-6 xl:px-4",
className,
)}
{...rest}
>
{children}
</section>
);
};
export default SectionWrapper;
Another component that is widley used is the <SectionTitle>
component. This component is used to display the title of the different sections of the website. It is used to keep the same layout across the different routes. This component should be used to display the title of the different sections of the website.
import {
type DetailedHTMLProps,
type FC,
type HTMLAttributes,
type PropsWithChildren,
} from "react";
import { cn } from "~/utils/utils";
interface ISectionTitle
extends DetailedHTMLProps<
HTMLAttributes<HTMLHeadingElement>,
HTMLHeadingElement
> {
center?: boolean;
}
const SectionTitle: FC<PropsWithChildren<ISectionTitle>> = ({
children,
center,
className = "",
...rest
}) => {
return (
<h1
className={cn(
`text-3xl font-semibold`,
center ? "text-center" : "",
className,
)}
{...rest}
>
{children}
</h1>
);
};
export default SectionTitle;
It's a simple component, but by using <Sectiontitle>
instead of writing this code every time, we can keep the same layout across the different routes and change it easily if we want to. This is a good example of how to use components to keep the same layout across the different routes and keeps the code clean and easy to read. This also keeps the code in the routes short, for example in src/pages/student-division/index.tsx
:
import { CommitteeType } from "@prisma/client";
import { type GetStaticProps, type NextPage } from "next";
import CommitteeLayout from "~/components/committees/CommitteeLayout";
import HeadLayout from "~/components/layout/HeadLayout";
import SecondaryTitle from "~/components/layout/SecondaryTitle";
import SectionTitle from "~/components/layout/SectionTitle";
import SectionWrapper from "~/components/layout/SectionWrapper";
import ssg from "~/server/api/helpers/ssg";
import { api } from "~/utils/api";
import { getCommitteeTypeStringFromEnum } from "~/utils/getCommitteeTypeStringFromEnum";
const Home: NextPage = () => {
const { data: committees } = api.committee.getAll.useQuery();
return (
<>
<HeadLayout title="Organ" />
<main>
<SectionWrapper>
<SectionTitle className="mb-8" center>
Sektionsorgan
</SectionTitle>
<div>
Sektionen består av flera olika organ som alla har olika uppgifter.
Kommittéer är organ som har en egen ekonomi och som har en specifik
uppgift. Utöver kommittéer finns det även utskott som är organ som
inte har en egen ekonomi och som har lite mindre uppgifter.
Sektionen kan också tillsätta tillfälliga arbetsgrupper för att
utreda specifika frågor. För att läsa mer om de olika organen kan du
klicka på dem nedan.
</div>
{Object.values(CommitteeType).map((committeeType) => {
const filterdCommitteesByType = committees?.filter((committee) => {
return committee.committeeType === committeeType;
});
if (filterdCommitteesByType?.length === 0) return null;
return (
<div key={committeeType}>
<SecondaryTitle className="mb-4" center>
{getCommitteeTypeStringFromEnum(committeeType, true)}
</SecondaryTitle>
<CommitteeLayout committees={filterdCommitteesByType} />
</div>
);
})}
</SectionWrapper>
</main>
</>
);
};
export default Home;
export const getStaticProps: GetStaticProps = async () => {
await ssg.committee.getAll.prefetch();
return {
props: {
trpcState: ssg.dehydrate(),
},
revalidate: 1,
};
};
This project is hosted on Github and is version controlled with Git. This means that all changes to the project are tracked and can be reverted if needed. The project is hosted on the Z-Teknologsektionen organization on Github. The project is divided into different branches, where the main
branch is the branch that is deployed to the website and dev
is deployed to the test website. The two branches are protected and you need to create a pull request to merge your changes into the dev
branch. main
is only updated from the dev
branch when the team feels the code is mature enough to go to production (usually every 2 weeks). This is to ensure that the code is reviewed before it is displayed on the website. The project is also divided into different issues and features where each feature or issue has their own branch. This is to keep track of what needs to be done and to keep the project organized. The project also has a project board where the different issues and features are placed in different columns depending on their status. This is to keep track of what needs to be done and what is being worked on.
All new branches should be a copy of the current dev
branch and be named based on the issue or feature that is being worked on. When the feature or issue is done, a pull request should be created and the code should be reviewed by another member of the team. When the code is reviewed and approved, it can be merged into the dev
branch. The code is then tested on the test website and if everything works as expected, it can be merged into the main
branch. This is to ensure that the code is reviewed before it is displayed on the website.
Feature branches should be named feat_nameOfFeature
and issue branches should be named fix_nameOfIssue
. This is to keep the project organized and to keep track of what needs to be done.
The project is deployed with Vercel which streamlines the deployment process. When a pull request is created and the code is reviewed and approved, the code is automatically deployed to the test website. This is to ensure that the code works as expected and to make it easier to test the code. When the code is merged into the main
branch, the code is automatically deployed to the production website. This is to ensure that the code is always up to date and that the website is always displaying the latest version of the code.
Before the project can be deployed to Vercel, it needs to pass some basic checks. These checks runs automatically on every commit to the repository. The checks are npm run lint
and npm run typecheck
. These checks are used to ensure that the code is clean and that there are no errors in the code. If the checks fail, the code might not be deployed to Vercel.
Prisma is a modern database toolkit used in this project to simplify database access with type safety and auto-generated queries. It's integrated into the project to manage database interactions efficiently. Prisma supports various databases, including PostgreSQL, MySQL, and SQLite, making it versatile for different project requirements.
The .env
file contains the DATABASE_URL
variable, which should be set to the connection string for the database. This project uses MongoDB Atlas which is a free cloud database. The connection string can be found in the MongoDB Atlas dashboard. The connection string should be placed in the .env
file and the database should be created in the MongoDB Atlas dashboard if not already present. MongoDB is a noSQL database and is different from SQL databases like PostgreSQL and MySQL. It can store data in a more flexible way and is a good choice for this project.
The Prisma schema schema.prisma
file defines the data model for the project. It includes the database tables, fields, relationships, and constraints. Prisma uses this schema to generate the database schema and TypeScript types for interacting with the database. Whenever something is changed in the schema, the database should be updated with the following command:
npx prisma generate
Here's an example of a simple Prisma schema:
model User {
id Int @id @default(autoincrement())
email String @unique
password String
name String?
createdAt DateTime @default(now())
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String
author User @relation(fields: [authorId], references: [id])
authorId Int
createdAt DateTime @default(now())
}
In this schema, a User
can have many posts while a post can only have one user. The @id
attribute is used to define the primary key, and the @default
attribute is used to define default values. The @unique
attribute is used to define unique constraints. The @relation
attribute is used to define relationships between tables.
tRPC and NextAuth.js are integral parts of the project, contributing to its functionality and security.
tRPC (Typed RPC) is used for defining typed APIs in TypeScript. It helps in defining API routes with type safety, making it easier to build and maintain APIs. In this project, tRPC is utilized for creating APIs for various functionalities, such as authentication and data retrieval. To add a new route to the API, a new file should be created in the src/server/api/routers
directory. The file should be named based on the route. The file should export a createTRPCRouter
function that defines the route and its functionality. You also need to add the implemented route in src/server/api/root.ts
. In the front end, you fetch the data from the api as per the following example:
import { api } from "~/utils/api";
const {
data: committees,
isLoading: isLoadingCommittees,
isError: isCommitteesError,
} = api.committee.getAllAsAuthed.useQuery();
<AdvancedDataTable
columns={committeeColumns}
data={committees || []}
error={isCommitteesError}
loading={isLoadingCommittees}
toolbar={CommitteeTableToolbar}
usePagination={true}
/>;
In this example the data is fetched from the api and the data is used to display a table. The api
object is used to fetch the data from the api with the useQuery
method. The data
property will not be available until all data is loaded and while it's loading the isLoading
property will be set to true. The isError
property is set to true if an error is thrown while quering the database.
NextAuth.js provides authentication solutions for Next.js applications. It supports various authentication providers like Google, Facebook, GitHub, etc., making it flexible for different authentication methods. It handles authentication flows, including OAuth, JWT, and session management, ensuring secure user authentication. In this project, NextAuth.js is used for user authentication with google and session management. In auth.ts
a user's roles and permissions are injected to the session so it can be used throughout the application. If more properties should be added to the session, it should be done in this file. To access the session properties the method useRequireAuth()
to get a user's session and useSession()
to get the session properties. If the user is unauthorized, the useRequireAuth()
method will redirect the user to the login page.
Tailwind CSS is a utility-first CSS framework used for styling the project. It offers a wide range of pre-built utility classes that can be directly applied to HTML elements, allowing for rapid UI development. Tailwind CSS is used all over this project and is preferred over traditional CSS for its simplicity and flexibility. If you are unsure about how to style something, look at the Tailwind CSS documentation or reach out to the other members of Webbgruppen.