Skip to content

Commit

Permalink
set up frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
avibn committed Feb 7, 2024
1 parent 2814420 commit 648c933
Show file tree
Hide file tree
Showing 10 changed files with 412 additions and 177 deletions.
17 changes: 17 additions & 0 deletions client/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "src/styles/globals.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}
211 changes: 79 additions & 132 deletions client/package-lock.json

Large diffs are not rendered by default.

13 changes: 9 additions & 4 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,24 @@
"lint": "next lint"
},
"dependencies": {
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"lucide-react": "^0.323.0",
"next": "14.1.0",
"react": "^18",
"react-dom": "^18",
"next": "14.1.0"
"tailwind-merge": "^2.2.1",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"typescript": "^5",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10.0.1",
"eslint": "^8",
"eslint-config-next": "14.1.0",
"postcss": "^8",
"tailwindcss": "^3.3.0",
"eslint": "^8",
"eslint-config-next": "14.1.0"
"typescript": "^5"
}
}
6 changes: 6 additions & 0 deletions client/src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
5 changes: 5 additions & 0 deletions client/src/models/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface User {
id: string;
name: string;
email: string;
}
49 changes: 49 additions & 0 deletions client/src/network/errors/httpErrors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Represents an HTTP error.
*/
export class HttpError extends Error {
/**
* Constructs a new instance of the HttpError class.
* @param message - Optional error message.
*/
constructor(message?: string) {
super(message);
this.name = this.constructor.name;
}
}

/**
* Status code: 400
*/
export class BadRequestError extends HttpError {}

/**
* Status code: 401
*/
export class UnauthorizedError extends HttpError {}

/**
* Status code: 403
*/
export class ForbiddenError extends HttpError {}

/**
* Status code: 404
*/
export class NotFoundError extends HttpError {}

/**
* Status code: 409
*/
export class ConflictError extends HttpError {}

// Map HTTP status code to error class
export const statusCodeErrorMap: {
[key: number]: new (message?: string) => HttpError;
} = {
400: BadRequestError,
401: UnauthorizedError,
403: ForbiddenError,
404: NotFoundError,
409: ConflictError,
};
64 changes: 64 additions & 0 deletions client/src/network/helpers/apiHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { cookies } from "next/headers";
import { statusCodeErrorMap } from "../errors/httpErrors";

/**
* Fetches data from the server.
* @param input - The URL to fetch.
* @param init - The optional RequestInit object for additional configuration.
* @param isServerRequest - Whether the request is made from the server.
* @returns A Promise that resolves to the Response object representing the fetched data.
* @throws Error if the base URL is not set, if fetching data fails, or if an HTTP error occurs.
*/
export async function fetchData(
input: RequestInfo,
init: RequestInit = {},
isServerRequest = false
) {
const baseURL = process.env.NEXT_PUBLIC_BASE_URL;

if (!baseURL) {
throw new Error("Base URL is not set.");
}

// Add json header if body is present
if (init.body) {
init.headers = {
...init.headers,
"Content-Type": "application/json",
};
}

// Add cookies if request from server
if (isServerRequest) {
init.headers = {
...init.headers,
Cookie: cookies().toString(),
};
}

// Fetch data from the server
let response;
try {
response = await fetch(baseURL + input, init);
} catch (error) {
throw new Error("Failed to fetch data");
}

// Check HTTP errors
if (!response.ok) {
const body = await response.json();
const errorMessage = body.error || "Unknown error occurred.";

// Map HTTP status code to error class
if (response.status in statusCodeErrorMap) {
throw new statusCodeErrorMap[response.status](errorMessage);
}

// Generic error
throw new Error(
"Something went wrong: " + response.status + " : " + errorMessage
);
}

return response;
}
41 changes: 41 additions & 0 deletions client/src/network/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { User } from "@/models/user";
import { fetchData } from "./helpers/apiHelper";

interface LoginBody {
email: string;
password: string;
}

interface RegisterBody {
name: string;
email: string;
password: string;
userType: "tenant" | "landlord";
}

export const loginClient = async (body: LoginBody): Promise<User> => {
const response = await fetchData("/users/login", {
method: "POST",
body: JSON.stringify(body),
});
return await response.json();
};

export const registerClient = async (body: RegisterBody): Promise<User> => {
const response = await fetchData("/users/signup", {
method: "POST",
body: JSON.stringify(body),
});
return await response.json();
};

export const logoutClient = async () => {
await fetchData("/users/logout", {
method: "POST",
});
};

export const getCurrentUserServer = async (): Promise<User> => {
const response = await fetchData("/users/me", {}, true);
return await response.json();
};
97 changes: 69 additions & 28 deletions client/src/styles/globals.css
Original file line number Diff line number Diff line change
@@ -1,35 +1,76 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;

/* Tailwind */
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;

--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;

--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;

--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;

--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;

--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;

--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;

/* Root */
@media (prefers-color-scheme: light) {
:root {
--text: #011307;
--background: #f8fdfa;
--primary: #2fb673;
--secondary: #83d6fa;
--accent: #4994f8;
}
}
@media (prefers-color-scheme: dark) {
:root {
--text: #ecfef2;
--background: #020805;
--primary: #49d08c;
--secondary: #05577a;
--accent: #0753b6;
}
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;

--radius: 0.5rem;
}

.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;

--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;

--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;

--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;

--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;

--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;

--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;

--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;

--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
}
}

body {
color: --text;
background: --background;

@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
Loading

0 comments on commit 648c933

Please sign in to comment.