Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/User Profile Admin Pages and Dots 1 #346

Merged
merged 27 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b4ab61d
chore: adds dots admin page
JohanHjelsethStorstad Oct 14, 2024
ee3210c
chore: adds Dot models to schema
JohanHjelsethStorstad Oct 14, 2024
6bb4c79
chore: adds authers and permissions for dots
JohanHjelsethStorstad Oct 14, 2024
1ef0469
chore: adds validation for dots
JohanHjelsethStorstad Oct 14, 2024
bd7dce5
feat: form and action to create a dot
JohanHjelsethStorstad Oct 14, 2024
2bd1b10
chore: improves styling
JohanHjelsethStorstad Oct 14, 2024
4a54c84
chore: adds boilerplate for dots-freeze periods admin
JohanHjelsethStorstad Oct 14, 2024
d1e97ab
chore: changes dot schema to have dotWrappers and dots
JohanHjelsethStorstad Oct 14, 2024
07a7f38
fix: increment dot expires at for each one created
JohanHjelsethStorstad Oct 14, 2024
b5bd7cc
chore: sets up pagin backend for dots
JohanHjelsethStorstad Oct 14, 2024
09c9882
chore: display dots to admin-users
JohanHjelsethStorstad Oct 14, 2024
729e7fc
fix: turns out timezoning is really hard. Fix European summer time
JohanHjelsethStorstad Oct 14, 2024
7a192b4
chore: small UI changes
JohanHjelsethStorstad Oct 14, 2024
0ac2d50
feat: adds use of query params to handle sorting of dots
JohanHjelsethStorstad Oct 14, 2024
b47f85b
fix: QueryParams stacking badly
JohanHjelsethStorstad Oct 14, 2024
aed012c
chore: improves dot-filter UI
JohanHjelsethStorstad Oct 14, 2024
1291bb5
refactor: makes profile pages use new auther system
JohanHjelsethStorstad Oct 15, 2024
1932065
refactor: better client side error handling
JohanHjelsethStorstad Oct 15, 2024
be1fb5f
feat: introduces UserNavigation for quick user based links
JohanHjelsethStorstad Oct 15, 2024
16fb4da
chore: makes the UserNavigation display work on mobile
JohanHjelsethStorstad Oct 15, 2024
a077a92
refactor: changes the user-settings stucture
JohanHjelsethStorstad Oct 16, 2024
24a4fc8
chore: changes styling on user-admin pages
JohanHjelsethStorstad Oct 17, 2024
2a8ce80
feat: displays dots on user page
JohanHjelsethStorstad Oct 17, 2024
a40f53a
feat: displays clarly if a dot is expired
JohanHjelsethStorstad Oct 17, 2024
3dc9e9f
style: lint
JohanHjelsethStorstad Oct 18, 2024
844fb2a
fix: mailList returns wrong type after ActionError type change
JohanHjelsethStorstad Oct 18, 2024
6b0b1ca
fix: faults with action return error type change - use createActionEr…
JohanHjelsethStorstad Oct 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading