Skip to content

Commit

Permalink
Merge pull request #18
Browse files Browse the repository at this point in the history
Create profile settings
  • Loading branch information
rokartur authored May 4, 2024
2 parents cd8ed4c + 5b8fa4f commit 6649518
Show file tree
Hide file tree
Showing 37 changed files with 1,529 additions and 198 deletions.
85 changes: 85 additions & 0 deletions backend/src/routes/user/delete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { ElysiaApp } from '@/app'
import { db } from '@/utils/db'
import { eq } from 'drizzle-orm'
import { meetings, sessions, users } from '@/db/schema'
import { lucia } from '@/utils/lucia'

export default (app: ElysiaApp) =>
app.get('/', async ({ set, cookie: { github_oauth_state, auth_session } }) => {
const validateRequest = async () => {
const sessionId = auth_session?.value ?? null

if (!sessionId)
return {
user: null,
session: null,
}

const { user, session } = await lucia.validateSession(sessionId)
try {
if (session && session.fresh) {
const sessionCookie = lucia.createSessionCookie(session.id)
auth_session.set({
value: sessionCookie.value,
httpOnly: sessionCookie.attributes.httpOnly,
secure: sessionCookie.attributes.secure,
sameSite: sessionCookie.attributes.sameSite,
path: sessionCookie.attributes.path,
maxAge: sessionCookie.attributes.maxAge,
})
}
if (!session) {
const sessionCookie = lucia.createBlankSessionCookie()
auth_session.set({
value: sessionCookie.value,
httpOnly: sessionCookie.attributes.httpOnly,
secure: sessionCookie.attributes.secure,
sameSite: sessionCookie.attributes.sameSite,
path: sessionCookie.attributes.path,
maxAge: sessionCookie.attributes.maxAge,
})
}
} catch (error: any) {
console.log(error)
}

return {
user,
session,
}
}

const userSession = await db.query.sessions.findFirst({ where: eq(sessions.id, auth_session.value) })

const { user, session } = await validateRequest()

if (user && session && userSession) {
github_oauth_state.set({
value: '',
httpOnly: true,
secure: true,
sameSite: 'strict',
path: '/',
maxAge: 0,
})

auth_session.set({
value: '',
httpOnly: true,
secure: true,
sameSite: 'strict',
path: '/',
maxAge: 0,
})

await db.delete(meetings).where(eq(meetings.userId, user.id)).returning()
await db.delete(sessions).where(eq(sessions.userId, user.id)).returning()
await db.delete(users).where(eq(users.id, user.id)).returning()

set.status = 200
set.headers.location = '/'
} else {
set.status = 401
return { message: 'unauthorized' }
}
})
2 changes: 1 addition & 1 deletion website/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset='UTF-8' />
<meta name='viewport' content='width=device-width, initial-scale=1' />
<title>FitPlan Connect</title>
<meta name='theme-color' content='#000000' />
<meta name='theme-color' content='#FFFFFF' />
<link rel='icon' type='image/svg+xml' href='https://' />
<meta name='google' content='notranslate' />
<link rel='canonical' href='https://' />
Expand Down
11 changes: 7 additions & 4 deletions website/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,23 @@ import { store } from '@/utils/store.ts';

gsap.registerPlugin(useGSAP, ScrollTrigger)

const AppSettings = lazy(() => import('@/pages/app.settings.tsx'))
const AppSettings = lazy(() => import('@/pages/app/app.settings.tsx'))

export default function App() {
useScrollTop()

return (
<Provider store={store}>
<HelmetProvider>
<Suspense fallback={<p>loading...</p>}>
<Suspense fallback={<p>loading</p>}>
<BrowserRouter>
<Header />
<Routes>
<Route path={'/'} element={<h1>siema</h1>} />
<Route path={'/app/calendar'} element={<AppSettings />} />
<Route path={'/'} element={<h1>landing</h1>} />
<Route path={'/app/calendar'} element={<h1>calendar</h1>} />
<Route path={'/app/trainers'} element={<h1>trainers</h1>} />
<Route path={'/app/billing'} element={<h1>billing</h1>} />
<Route path={'/app/settings'} element={<AppSettings />} />
</Routes>
</BrowserRouter>
</Suspense>
Expand Down
7 changes: 7 additions & 0 deletions website/src/assets/icons/IconX.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const IconX = () => (
<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20' fill='none'>
<path d='M5 15L15 5M5 5L15 15' stroke='#6B7280' strokeWidth='1.5' strokeLinecap='round' strokeLinejoin='round' />
</svg>
)

export default IconX
113 changes: 113 additions & 0 deletions website/src/components/alertDialog/alertDialog.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
@import "@/styles/variables";

.alertDialogBackground {
position: fixed;
z-index: 900;
inset: 0;

width: 100vw;
height: 100vh;
overflow: hidden;

display: flex;
justify-content: center;
align-items: center;

background-color: rgb(255, 255, 255, .8);
backdrop-filter: $blur-small;

animation: fadeShowModalBackground .15s ease-out backwards;
}

@keyframes fadeShowModalBackground {
from {
opacity: 0
}
to {
opacity: 1
}
}

.alertDialog {
padding: 24px;

width: 100%;
max-width: 512px;

border-radius: 16px;
background-color: #FFF;
box-shadow: $drop-shadow-medium;

animation: showModalScaleUp .15s ease-out backwards;

.alertDialogContainer {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 32px;

.alertDialogContent {
width: 100%;

position: relative;

display: flex;
flex-direction: column;
align-items: flex-start;
gap: 20px;

.alertDialogClose {
position: absolute;
inset: 0 0 auto auto;
cursor: pointer;
}

.alertDialogIcon {
padding: 8px;

width: 48px;
height: 48px;

display: flex;
justify-content: center;
align-items: center;

border-radius: 24px;

background-color: $primary-50;
}

.alertDialogText {
display: flex;
flex-direction: column;
gap: 8px;

p:nth-child(1) {
color: $neutral-900;
}

p:nth-child(2) {
color: $neutral-500;
}
}
}

.alertDialogActions {
width: 100%;

display: flex;
justify-content: flex-end;
align-items: flex-end;
gap: 12px;
}
}
}

@keyframes showModalScaleUp {
from {
transform: scale(.9)
}
to {
transform: scale(1)
}
}
118 changes: 118 additions & 0 deletions website/src/components/alertDialog/alertDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import styles from './alertDialog.module.scss'
import { useEffect, useRef, useState } from 'react'
import { gsap } from 'gsap'
import { Button } from '@/components/button/button.tsx'
import { Paragraph } from '@/components/typography/paragraph.tsx'
import IconX from '@/assets/icons/IconX.tsx'

type AlertDialogTypes = {
icon?: JSX.Element
title?: string
description?: string
isOpen: boolean
onClose: () => void
onCancel: () => void
onConfirm: () => void
onAccept?: () => void
closeWhenClickBackground?: boolean
closeWhenClickEscape?: boolean
labelOnCancel?: string
labelOnConfirm?: string
}

export const AlertDialog = ({
icon,
title = 'Title',
description = 'Description',
isOpen,
onClose,
onConfirm,
closeWhenClickBackground = false,
closeWhenClickEscape = false,
labelOnCancel = 'Cancel',
labelOnConfirm = 'Confirm',
}: AlertDialogTypes) => {
const [isClose, setIsClose] = useState(false)
const body = document.querySelector<HTMLBodyElement>('body')
const alertDialogContainer = useRef<HTMLDivElement | null>(null)

useEffect(() => {
if (closeWhenClickBackground) {
document.addEventListener('click', (event: MouseEvent) => {
const target = event.target as Node
if (alertDialogContainer.current) {
if (
!alertDialogContainer.current.contains(target) ||
(target.contains(alertDialogContainer.current) && target !== alertDialogContainer.current)
) {
onClose()
} else {
return alertDialogContainer
}
}
})
}

if (closeWhenClickEscape) {
document.addEventListener('keydown', (event: KeyboardEvent) => {
if (event.key === 'Escape' && isOpen) {
onClose()
}
})
}

if (!isOpen) {
if (isClose) {
gsap.fromTo(
'#alertDialog',
{ opacity: 1, scale: 1 },
{ opacity: 0, scale: 0.9, duration: 0.15, ease: 'ease-out' },
)
gsap.fromTo('#alertDialogBackground', { opacity: 1 }, { opacity: 0, duration: 0.15, ease: 'ease-out' })
setTimeout(() => setIsClose(false), 400)
}
} else {
setIsClose(true)
}
}, [isOpen])

if (isClose) {
body?.classList.add('tempDisableScroll')
} else {
body?.classList.remove('tempDisableScroll')
}

if (isClose) {
return (
<div id={'alertDialogBackground'} className={styles.alertDialogBackground}>
<div id={'alertDialog'} className={styles.alertDialog} ref={alertDialogContainer}>
<div className={styles.alertDialogContainer}>
<div className={styles.alertDialogContent}>
<button className={styles.alertDialogClose} onClick={onClose}>
<IconX />
</button>

{icon ? <span className={styles.alertDialogIcon}>{icon}</span> : null}

<div className={styles.alertDialogText}>
<Paragraph size={'large'} weight={'medium-weight'}>
{title}
</Paragraph>
<Paragraph size={'small'} weight={'regular-weight'}>
{description}
</Paragraph>
</div>
</div>

<div className={styles.alertDialogActions}>
<Button type={'tertiary'} size={'small'} label={labelOnCancel} onClick={onClose} />
<Button type={'secondary'} size={'small'} label={labelOnConfirm} onClick={onConfirm} />
</div>
</div>
</div>
</div>
)
} else {
return null
}
}
21 changes: 21 additions & 0 deletions website/src/components/animateWrapper/animateWrapper.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@import '@/styles/variables';

.animateWrapper {
width: 100%;
min-height: calc(100vh - 73px);

animation: fade .3s;
}

@keyframes fade {
from {
opacity: 0;
transform-origin: top;
transform: translateY(10px) scale(.9);
}
to {
opacity: 1;
transform-origin: top;
transform: translateY(0) scale(1);
}
}
8 changes: 8 additions & 0 deletions website/src/components/animateWrapper/animateWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import styles from './animateWrapper.module.scss';
import { FC, ReactNode } from 'react'

type AnimateWrapperProps = {
children: ReactNode
}

export const AnimateWrapper: FC<AnimateWrapperProps> = ({ children }) => <div className={styles.animateWrapper}>{children}</div>
Loading

0 comments on commit 6649518

Please sign in to comment.