Skip to content

Commit

Permalink
Merge pull request #346 from vevcom/feat/dots
Browse files Browse the repository at this point in the history
Feat/User Profile Admin Pages and Dots 1
  • Loading branch information
theodorklauritzen authored Oct 24, 2024
2 parents 4e477cf + 6b0b1ca commit f81f9e5
Show file tree
Hide file tree
Showing 88 changed files with 1,529 additions and 284 deletions.
8 changes: 3 additions & 5 deletions src/actions/Types.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import type { AuthStatus } from '@/auth/getUser'
import type { ServerErrorCode, ErrorMessage } from '@/services/error'

export type ActionErrorCode = ServerErrorCode | AuthStatus
import type { ErrorMessage, ErrorCode } from '@/services/error'

export type ActionReturnError = {
success: false,
errorCode: ActionErrorCode,
errorCode: ErrorCode,
httpCode: number,
error?: ErrorMessage[],
}

Expand Down
5 changes: 5 additions & 0 deletions src/actions/dots/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use server'
import { Action } from '@/actions/Action'
import { Dots } from '@/services/dots'

export const createDotAction = Action(Dots.create)
7 changes: 7 additions & 0 deletions src/actions/dots/read.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use server'
import { ActionNoData } from '@/actions/Action'
import { Dots } from '@/services/dots'

export const readDotPage = ActionNoData(Dots.readPage)

export const readDotWrapperForUser = ActionNoData(Dots.readWrapperForUser)
17 changes: 14 additions & 3 deletions src/actions/error.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,29 @@
import type { ErrorMessage } from '@/services/error'
import { errorCodes, type ErrorCode, type ErrorMessage } from '@/services/error'
import type { AuthStatus } from '@/auth/getUser'
import type { SafeParseError } from 'zod'
import type { ActionReturnError, ActionErrorCode } from './Types'
import type { ActionReturnError } from './Types'

export function createActionError(errorCode: ActionErrorCode, error?: string | ErrorMessage[]): ActionReturnError {
export function createActionError(errorCode: ErrorCode | AuthStatus, error?: string | ErrorMessage[]): ActionReturnError {
if (errorCode === 'AUTHORIZED' || errorCode === 'AUTHORIZED_NO_USER') {
return {
success: false,
errorCode: 'UNKNOWN ERROR',
httpCode: 500,
error: typeof error === 'string' ? [{ message: error }] : error,
}
}
return {
success: false,
errorCode,
httpCode: errorCodes.find(e => e.name === errorCode)?.httpCode ?? 500,
error: typeof error === 'string' ? [{ message: error }] : error,
}
}

export function createZodActionError<T>(parse: SafeParseError<T>): ActionReturnError {
return {
success: false,
httpCode: 400,
errorCode: 'BAD PARAMETERS',
error: parse.error.issues,
}
Expand Down
4 changes: 2 additions & 2 deletions src/actions/safeServerCall.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createActionError } from './error'
import { ServerError } from '@/services/error'
import { Smorekopp } from '@/services/error'
import type { ActionReturn } from './Types'

/**
Expand All @@ -17,7 +17,7 @@ export async function safeServerCall<T>(call: () => Promise<T>): Promise<ActionR
data
}
} catch (error) {
if (error instanceof ServerError) {
if (error instanceof Smorekopp) {
return createActionError(error.errorCode, error.errors)
}
return createActionError('UNKNOWN ERROR', 'unknown error')
Expand Down
15 changes: 15 additions & 0 deletions src/app/_components/Date/Date.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use client'
import { displayDate } from '@/dates/displayDate'

type PropTypes = {
date: Date
}

/**
* Just a wrapper for the displayDate function. Importantly it is client side rendered to use the client's timezone.
* @param date - The date to display
* @returns the date in jsx
*/
export default function Date({ date }: PropTypes) {
return displayDate(date)
}
8 changes: 4 additions & 4 deletions src/app/_components/Event/EventTagsAdmin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ export default function EventTagsAdmin({

const removeFromUrl = (tag: string) => (selectedTags.length === 1 ?
baseUrl :
baseUrl + QueryParams.eventTags.encodeUrl(
`${baseUrl}?${QueryParams.eventTags.encodeUrl(
selectedTags.filter(t => t.name !== tag).map(t => t.name)
))
const addToUrl = (tag: string) => baseUrl + QueryParams.eventTags.encodeUrl(
)}`)
const addToUrl = (tag: string) => `${baseUrl}?${QueryParams.eventTags.encodeUrl(
[...selectedTags.map(t => t.name), tag]
)
)}`
return (
<div className={styles.EventTagsAdmin}>
<h1>Tagger</h1>
Expand Down
3 changes: 2 additions & 1 deletion src/app/_components/NavBar/MobileNavBar.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
height: 25px;
}
&.magicHat {
> * {
position: relative;
> .image {
filter: invert(1);
}
}
Expand Down
18 changes: 12 additions & 6 deletions src/app/_components/NavBar/MobileNavBar.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import getNavItems from './navDef'
import styles from './MobileNavBar.module.scss'
import Menu from './Menu'
import UserNavigation from './UserNavigation'
import SpecialCmsImage from '@/components/Cms/CmsImage/SpecialCmsImage'
import EditModeSwitch from '@/components/EditModeSwitch/EditModeSwitch'
import { getUser } from '@/auth/getUser'
import Link from 'next/link'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faBars } from '@fortawesome/free-solid-svg-icons'
import type { PropTypes } from './NavBar'

export default async function MobileNavBar() {
const { user } = await getUser()
export default async function MobileNavBar({ profile }: PropTypes) {
const user = profile?.user ?? null
const isLoggedIn = user !== null
const applicationPeriod = false //TODO
const isAdmin = true //TODO
Expand All @@ -34,9 +35,14 @@ export default async function MobileNavBar() {
</SpecialCmsImage>
</div>
<div className={styles.magicHat}>
<SpecialCmsImage special="MOBILE_NAV_LOGIN_BUTTON" width={25} height={25} alt="log in button">
<Link className={styles.imagelink} href={isLoggedIn ? '/users/me' : '/login'} />
</SpecialCmsImage>
<SpecialCmsImage
special="MOBILE_NAV_LOGIN_BUTTON"
width={25}
height={25}
alt="log in button"
className={styles.image}
/>
<UserNavigation profile={profile} />
</div>
<Menu items={itemsForMenu} openBtnContent={
<div className={styles.menuBtn}>
Expand Down
10 changes: 1 addition & 9 deletions src/app/_components/NavBar/NavBar.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,7 @@
}
}
.magicHat {
a {
width: 100%;
height: 100%;
position: absolute;
top: calc(-1*ohma.$rounding);
left: calc(-1*ohma.$rounding);
width: calc(100% + 2*ohma.$rounding);
height: calc(100% + 2*ohma.$rounding);
}
position: relative;
@include ohma.btn(ohma.$colors-primary);
}
}
Expand Down
16 changes: 10 additions & 6 deletions src/app/_components/NavBar/NavBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@ import Item from './Item'
import styles from './NavBar.module.scss'
import Menu from './Menu'
import getNavItems from './navDef'
import UserNavigation from './UserNavigation'
import EditModeSwitch from '@/components/EditModeSwitch/EditModeSwitch'
import SpecialCmsImage from '@/components/Cms/CmsImage/SpecialCmsImage'
import { getUser } from '@/auth/getUser'
import Link from 'next/link'
import type { Profile } from '@/services/users/Types'

export default async function NavBar() {
const { user } = await getUser()
export type PropTypes = {
profile: Profile | null
}

export default async function NavBar({ profile }: PropTypes) {
const user = profile?.user ?? null
const isLoggedIn = user !== null

//temporary
Expand Down Expand Up @@ -51,9 +56,8 @@ export default async function NavBar() {
width={25}
height={25}
alt="log in button"
>
<Link href={isLoggedIn ? '/users/me' : '/login'} />
</SpecialCmsImage>
/>
<UserNavigation profile={profile} />
</div>
</li>
</ul>
Expand Down
84 changes: 84 additions & 0 deletions src/app/_components/NavBar/UserNavigation.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
@use '@/styles/ohma';

.UserNavigation {
@include ohma.screenLg {
position: absolute;
bottom: 0;
right: 0;
transform: translateY(calc(100% + .5em));
min-width: 300px;
max-height: 90vh;
@include ohma.boxShadow();
@include ohma.round();
}
@include ohma.screenMobile {
z-index: -1;
padding: 1em;
padding-top: 3em;
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: calc(100vh - 50px);
}
background-color: ohma.$colors-gray-200;
h2 {
color: ohma.$colors-black;
font-size: ohma.$fonts-xl;
}
display: flex;
flex-direction: column;
align-items: center;
.logout {
margin-top: .5em;
width: 100%;
> button {
width: 100%;
margin: 0;
}
}

.navs {
margin-top: 1em;
width: 100%;
display: flex;
flex-flow: row wrap;
gap: 1em;
> * {
flex: 1 1 calc(50% - 1em);
@include ohma.round();
background-color: ohma.$colors-secondary;
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
color: ohma.$colors-white;
transition: color .6s;
svg {
width: 20px;
height: 20px;
color: ohma.$colors-white;
transition: color .6s;
margin: .3em;
}
&:hover {
color: ohma.$colors-black;
svg {
color: ohma.$colors-black;
}
}
}
}
}

.hidden {
opacity: 0;
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
&:hover {
cursor: pointer;
}
}
67 changes: 67 additions & 0 deletions src/app/_components/NavBar/UserNavigation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
'use client'
import styles from './UserNavigation.module.scss'
import ProfilePicture from '@/components/User/ProfilePicture'
import BorderButton from '@/UI/BorderButton'
import useClickOutsideRef from '@/hooks/useClickOutsideRef'
import useOnNavigation from '@/hooks/useOnNavigation'
import { faCog, faDotCircle, faMoneyBill, faSignOut, faUser } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import Link from 'next/link'
import { useState } from 'react'
import type { Profile } from '@/services/users/Types'

type PropTypes = {
profile: Profile | null
}

/**
* This component either renders an empty link with a href to /login page if there is no profile
* Else it renders a usefull component for a logged in user.
* @param profile - The profile of the user
* @returns
*/
export default function UserNavigation({ profile }: PropTypes) {
const [isMenuOpen, setIsMenuOpen] = useState(false)
const ref = useClickOutsideRef(() => setIsMenuOpen(false))
useOnNavigation(() => setIsMenuOpen(false))

if (!profile || !profile.user) {
return <Link className={styles.hidden} href="/login" />
}

if (!isMenuOpen) {
return <button className={styles.hidden} onClick={() => setIsMenuOpen(!isMenuOpen)} />
}

return (
<div ref={ref} className={styles.UserNavigation}>
<ProfilePicture profileImage={profile.user.image} width={180} />
<h2>{profile.user.firstname} {profile.user.lastname}</h2>

<Link href="/logout" className={styles.logout}>
<BorderButton color="secondary">
<FontAwesomeIcon icon={faSignOut} /> <p>Logg ut</p>
</BorderButton>
</Link>

<div className={styles.navs}>
<Link href="/users/me">
<FontAwesomeIcon icon={faUser} />
<p>Profil</p>
</Link>
<Link href="/users/me/dots">
<FontAwesomeIcon icon={faDotCircle} />
<p>Prikker</p>
</Link>
<Link href="/users/me/money">
<FontAwesomeIcon icon={faMoneyBill} />
<p>Konto</p>
</Link>
<Link href="/users/me/settings">
<FontAwesomeIcon icon={faCog} />
<p>Instillinger</p>
</Link>
</div>
</div>
)
}
14 changes: 14 additions & 0 deletions src/app/_components/User/ProfilePicture.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
@use '@/styles/ohma';

.ProfilePicture {
display: flex;
justify-content: center;

.image {
border-radius: 50%;
border: 3px solid ohma.$colors-white;
background-color: ohma.$colors-white;
aspect-ratio: 1;
object-fit: cover;
}
}
16 changes: 16 additions & 0 deletions src/app/_components/User/ProfilePicture.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import styles from './ProfilePicture.module.scss'
import Image from '@/components/Image/Image'
import type { Image as ImageT } from '@prisma/client'

type PropTypes = {
profileImage: ImageT,
width: number,
}

export default function ProfilePicture({ profileImage, width }: PropTypes) {
return (
<div className={styles.ProfilePicture}>
<Image className={styles.image} image={profileImage} width={width}/>
</div>
)
}
Loading

0 comments on commit f81f9e5

Please sign in to comment.