diff --git a/comty.js b/comty.js
index 0dd24fee..fb4d6665 160000
--- a/comty.js
+++ b/comty.js
@@ -1 +1 @@
-Subproject commit 0dd24fee4a231dbb41d5b14ff92b67d8f14cb5a2
+Subproject commit fb4d666576a937eaad4ea69f9e5a13778f06b3f5
diff --git a/packages/app/package.json b/packages/app/package.json
index fbde7af4..a2a085b5 100755
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -1,90 +1,91 @@
{
- "name": "@comty/app",
- "version": "1.44.0@alpha",
- "license": "ComtyLicense",
- "main": "electron/main",
- "type": "module",
- "author": "RageStudio",
- "description": "A prototype of a social network.",
- "scripts": {
- "dev": "vite",
- "build": "vite build",
- "preview": "vite preview",
- "release": "node ./scripts/release.js",
- "postinstall": "./scripts/postinstall.sh",
- "eslint": "eslint"
- },
- "dependencies": {
- "@ant-design/icons": "^5.4.0",
- "@dnd-kit/core": "^6.0.8",
- "@dnd-kit/modifiers": "^9.0.0",
- "@dnd-kit/sortable": "^7.0.2",
- "@dnd-kit/utilities": "^3.2.2",
- "@emotion/react": "^11.13.0",
- "@emotion/styled": "^11.13.0",
- "@ffmpeg/ffmpeg": "^0.12.10",
- "@ffmpeg/util": "^0.12.1",
- "@mui/material": "^5.11.9",
- "@ragestudio/cordova-nfc": "^1.2.0",
- "@ragestudio/vessel": "^0.23.1",
- "@sentry/browser": "^7.64.0",
- "@tauri-apps/api": "^1.5.4",
- "@tsmx/human-readable": "^1.0.7",
- "antd": "^5.20.6",
- "axios": "^1.7.7",
- "bear-react-carousel": "^4.0.10-alpha.0",
- "classnames": "2.3.1",
- "comty.js": "^0.68.0",
- "d3": "^7.9.0",
- "dashjs": "^5.0.3",
- "dompurify": "^3.0.0",
- "fast-average-color": "^9.2.0",
- "fuse.js": "6.5.3",
- "hls.js": "^1.5.17",
- "howler": "2.2.3",
- "i18next": "21.6.6",
- "js-cookie": "3.0.1",
- "jsmediatags": "^3.9.7",
- "lottie-react": "^2.4.0",
- "luxon": "^3.0.4",
- "mime": "^3.0.0",
- "moment": "2.29.4",
- "motion": "^12.4.2",
- "music-metadata": "^11.2.1",
- "plyr": "^3.7.8",
- "prop-types": "^15.8.1",
- "qs": "^6.14.0",
- "react": "18.3.1",
- "react-beautiful-dnd": "^13.1.1",
- "react-color": "2.19.3",
- "react-countup": "^6.4.1",
- "react-dom": "18.3.1",
- "react-fast-marquee": "^1.3.5",
- "react-i18next": "11.15.3",
- "react-icons": "^5.4.0",
- "react-lazy-load-image-component": "^1.5.4",
- "react-markdown": "^8.0.3",
- "react-modal-image": "^2.6.0",
- "react-player": "^2.16.0",
- "react-rnd": "^10.4.14",
- "react-transition-group": "^4.4.5",
- "react-useanimations": "^2.10.0",
- "remark-gfm": "^3.0.1",
- "rxjs": "^7.5.5",
- "store": "^2.0.12",
- "swapy": "^1.0.5",
- "ua-parser-js": "^1.0.36",
- "vaul": "^1.1.2",
- "vite": "^6.2.6"
- },
- "devDependencies": {
- "@eslint/js": "^9.26.0",
- "@octokit/rest": "^21.1.1",
- "7zip-min": "1.4.3",
- "dotenv": "16.0.3",
- "eslint": "^9.26.0",
- "eslint-plugin-react": "^7.37.5",
- "form-data": "^4.0.0",
- "globals": "^16.1.0"
- }
+ "name": "@comty/app",
+ "version": "1.44.1@alpha",
+ "license": "ComtyLicense",
+ "main": "electron/main",
+ "type": "module",
+ "author": "RageStudio",
+ "description": "A prototype of a social network.",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview",
+ "release": "node ./scripts/release.js",
+ "postinstall": "./scripts/postinstall.sh",
+ "eslint": "eslint"
+ },
+ "dependencies": {
+ "@ant-design/icons": "^5.4.0",
+ "@dnd-kit/core": "^6.0.8",
+ "@dnd-kit/modifiers": "^9.0.0",
+ "@dnd-kit/sortable": "^7.0.2",
+ "@dnd-kit/utilities": "^3.2.2",
+ "@emotion/react": "^11.13.0",
+ "@emotion/styled": "^11.13.0",
+ "@ffmpeg/ffmpeg": "^0.12.10",
+ "@ffmpeg/util": "^0.12.1",
+ "@mui/material": "^5.11.9",
+ "@ragestudio/cordova-nfc": "^1.2.0",
+ "@ragestudio/vessel": "^0.23.1",
+ "@sentry/browser": "^7.64.0",
+ "@tauri-apps/api": "^1.5.4",
+ "@tsmx/human-readable": "^1.0.7",
+ "antd": "^5.20.6",
+ "axios": "^1.7.7",
+ "bear-react-carousel": "^4.0.10-alpha.0",
+ "classnames": "2.3.1",
+ "comty.js": "^0.68.1",
+ "d3": "^7.9.0",
+ "dashjs": "^5.0.3",
+ "dompurify": "^3.0.0",
+ "fast-average-color": "^9.2.0",
+ "fuse.js": "6.5.3",
+ "hls.js": "^1.5.17",
+ "howler": "2.2.3",
+ "i18next": "21.6.6",
+ "js-cookie": "3.0.1",
+ "jsmediatags": "^3.9.7",
+ "lottie-react": "^2.4.0",
+ "luxon": "^3.0.4",
+ "mime": "^3.0.0",
+ "moment": "2.29.4",
+ "motion": "^12.4.2",
+ "music-metadata": "^11.2.1",
+ "plyr": "^3.7.8",
+ "prop-types": "^15.8.1",
+ "qs": "^6.14.0",
+ "react": "18.3.1",
+ "react-beautiful-dnd": "^13.1.1",
+ "react-color": "2.19.3",
+ "react-countup": "^6.4.1",
+ "react-dom": "18.3.1",
+ "react-fast-marquee": "^1.3.5",
+ "react-i18next": "11.15.3",
+ "react-icons": "^5.4.0",
+ "react-lazy-load-image-component": "^1.5.4",
+ "react-markdown": "^8.0.3",
+ "react-modal-image": "^2.6.0",
+ "react-player": "^2.16.0",
+ "react-rnd": "^10.4.14",
+ "react-transition-group": "^4.4.5",
+ "react-turnstile": "^1.1.4",
+ "react-useanimations": "^2.10.0",
+ "remark-gfm": "^3.0.1",
+ "rxjs": "^7.5.5",
+ "store": "^2.0.12",
+ "swapy": "^1.0.5",
+ "ua-parser-js": "^1.0.36",
+ "vaul": "^1.1.2",
+ "vite": "^6.2.6"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.26.0",
+ "@octokit/rest": "^21.1.1",
+ "7zip-min": "1.4.3",
+ "dotenv": "16.0.3",
+ "eslint": "^9.26.0",
+ "eslint-plugin-react": "^7.37.5",
+ "form-data": "^4.0.0",
+ "globals": "^16.1.0"
+ }
}
diff --git a/packages/app/src/components/Player/SeekBar/index.less b/packages/app/src/components/Player/SeekBar/index.less
index 458e5317..c71b04f6 100755
--- a/packages/app/src/components/Player/SeekBar/index.less
+++ b/packages/app/src/components/Player/SeekBar/index.less
@@ -1,3 +1,11 @@
+&.mobile {
+ .player-seek_bar {
+ .slider-container {
+ height: 10px;
+ }
+ }
+}
+
.player-seek_bar {
z-index: 330;
diff --git a/packages/app/src/contexts/WithPlayerContext/index.jsx b/packages/app/src/contexts/WithPlayerContext/index.jsx
index 1fbe4c9a..ee453f62 100755
--- a/packages/app/src/contexts/WithPlayerContext/index.jsx
+++ b/packages/app/src/contexts/WithPlayerContext/index.jsx
@@ -40,9 +40,7 @@ export const usePlayerStateContext = (updater) => {
app.cores.player.eventBus().on("player.state.update", handleStateChange)
return () => {
- app.cores.player
- .eventBus()
- .off("player.state.update", handleStateChange)
+ app.cores.player.eventBus().off("player.state.update", handleStateChange)
}
}, [])
@@ -56,9 +54,7 @@ export class WithPlayerContext extends React.Component {
events = {
"player.state.update": async (state) => {
- if (state !== this.state) {
- this.setState(state)
- }
+ this.setState(state)
},
}
diff --git a/packages/app/src/layout.jsx b/packages/app/src/layout.jsx
index e52fbd5f..1a935dad 100755
--- a/packages/app/src/layout.jsx
+++ b/packages/app/src/layout.jsx
@@ -23,9 +23,7 @@ export default class Layout extends React.PureComponent {
const transitionLayer = document.getElementById("transitionLayer")
if (!transitionLayer) {
- console.warn(
- "transitionLayer not found, no animation will be played",
- )
+ console.warn("transitionLayer not found, no animation will be played")
return false
}
@@ -42,9 +40,7 @@ export default class Layout extends React.PureComponent {
const transitionLayer = document.getElementById("transitionLayer")
if (!transitionLayer) {
- console.warn(
- "transitionLayer not found, no animation will be played",
- )
+ console.warn("transitionLayer not found, no animation will be played")
return false
}
@@ -114,10 +110,7 @@ export default class Layout extends React.PureComponent {
)
},
toggleMobileStyle: (to) => {
- return this.layoutInterface.toggleRootContainerClassname(
- "mobile",
- to,
- )
+ return this.layoutInterface.toggleRootContainerClassname("mobile", to)
},
toggleReducedAnimations: (to) => {
return this.layoutInterface.toggleRootContainerClassname(
@@ -150,17 +143,14 @@ export default class Layout extends React.PureComponent {
)
},
toggleRootContainerClassname: (classname, to) => {
- const root = document.documentElement
+ const root = document.documentElement
if (!root) {
console.error("root not found")
return false
}
- to =
- typeof to === "boolean"
- ? to
- : !root.classList.contains(classname)
+ to = typeof to === "boolean" ? to : !root.classList.contains(classname)
if (root.classList.contains(classname) === to) {
// ignore
@@ -208,10 +198,9 @@ export default class Layout extends React.PureComponent {
if (this.state.renderError) {
if (this.props.staticRenders?.RenderError) {
- return React.createElement(
- this.props.staticRenders?.RenderError,
- { error: this.state.renderError },
- )
+ return React.createElement(this.props.staticRenders?.RenderError, {
+ error: this.state.renderError,
+ })
}
return JSON.stringify(this.state.renderError)
diff --git a/packages/app/src/layouts/components/@mobile/bottomBar/index.jsx b/packages/app/src/layouts/components/@mobile/bottomBar/index.jsx
index 84e4be0d..ed45c4c7 100755
--- a/packages/app/src/layouts/components/@mobile/bottomBar/index.jsx
+++ b/packages/app/src/layouts/components/@mobile/bottomBar/index.jsx
@@ -159,7 +159,10 @@ const AccountButton = React.forwardRef((props, ref) => {
>
{user ? (
-
+
) : (
createIconRender("FiLogin")
)}
@@ -168,6 +171,8 @@ const AccountButton = React.forwardRef((props, ref) => {
)
})
+AccountButton.displayName = "AccountButton"
+
export class BottomBar extends React.Component {
static contextType = Context
@@ -366,9 +371,7 @@ export class BottomBar extends React.Component {
}
const heightValue = Number(
- app.cores.style
- .getDefaultVar("bottom-bar-height")
- .replace("px", ""),
+ app.cores.style.getDefaultVar("bottom-bar-height").replace("px", ""),
)
return (
@@ -409,10 +412,7 @@ export class BottomBar extends React.Component {
@@ -441,9 +441,7 @@ export class BottomBar extends React.Component {
})
}}
>
-
- {createIconRender("FiHome")}
-
+
{createIconRender("FiHome")}
-
- {createIconRender("FiSearch")}
-
+
{createIconRender("FiSearch")}
diff --git a/packages/app/src/pages/auth/forms/register/index.jsx b/packages/app/src/pages/auth/forms/register/index.jsx
index f4a42f14..d1f6afeb 100755
--- a/packages/app/src/pages/auth/forms/register/index.jsx
+++ b/packages/app/src/pages/auth/forms/register/index.jsx
@@ -12,8 +12,9 @@ import UsernameStep from "./steps/username"
import PasswordStep from "./steps/password"
import EmailStep from "./steps/email"
import TOSStep from "./steps/tos"
+import CaptchaStep from "./steps/captcha"
-const steps = [UsernameStep, PasswordStep, EmailStep, TOSStep]
+const steps = [UsernameStep, PasswordStep, EmailStep, TOSStep, CaptchaStep]
const RegisterForm = (props) => {
const [finishing, setFinishing] = React.useState(false)
@@ -35,6 +36,7 @@ const RegisterForm = (props) => {
password: stepsValues.password,
email: stepsValues.email,
tos: stepsValues.tos,
+ captcha: stepsValues.captcha,
}).catch((err) => {
setFinishSuccess(false)
setFinishing(false)
@@ -107,8 +109,8 @@ const RegisterForm = (props) => {
<>
👋 Hi! Nice to meet you
- Tell us some basic information to get started
- creating your account.
+ Tell us some basic information to get started creating your
+ account.
>
)}
@@ -116,8 +118,7 @@ const RegisterForm = (props) => {
{!finishSuccess && !finishing && step > 0 && (
<>
- {currentStepData?.icon &&
- createIconRender(currentStepData.icon)}
+ {currentStepData?.icon && createIconRender(currentStepData.icon)}
{currentStepData?.title}
@@ -150,10 +151,7 @@ const RegisterForm = (props) => {
Welcome abord!
-
- One last step, we need you to login with your new
- account.
-
+
One last step, we need you to login with your new account.
{
)}
{finishError && (
-
+
)}
{!finishSuccess && !finishing && (
{step === 0 && (
-
props.setActiveKey("selector")}
- >
+ props.setActiveKey("selector")}>
Cancel
)}
{step > 0 && (
- prevStep()}>
- Back
-
+ prevStep()}>Back
)}
{
+ return (
+ {
+ props.updateValue(token)
+ }}
+ />
+ )
+}
+
+export default {
+ key: "captcha",
+ title: "Step 4",
+ icon: "FiLock",
+ description:
+ "We need you to prove that you are a human. Please enter the captcha below.",
+ required: true,
+ content: CaptchaStepComponent,
+}
diff --git a/packages/app/src/pages/auth/forms/register/steps/email/index.jsx b/packages/app/src/pages/auth/forms/register/steps/email/index.jsx
index 1b8940a5..04a7474e 100755
--- a/packages/app/src/pages/auth/forms/register/steps/email/index.jsx
+++ b/packages/app/src/pages/auth/forms/register/steps/email/index.jsx
@@ -4,87 +4,103 @@ import * as antd from "antd"
import AuthModel from "@models/auth"
const EmailStepComponent = (props) => {
- const [email, setEmail] = React.useState(props.currentValue ?? "")
+ const [email, setEmail] = React.useState(props.currentValue ?? "")
- const [loading, setLoading] = React.useState(false)
- const [validFormat, setValidFormat] = React.useState(null)
- const [emailAvailable, setEmailAvailable] = React.useState(null)
+ const [loading, setLoading] = React.useState(false)
+ const [validFormat, setValidFormat] = React.useState(null)
+ const [emailAvailable, setEmailAvailable] = React.useState(null)
- const isValid = () => {
- return email.length > 0 && validFormat && emailAvailable
- }
+ const isValid = () => {
+ return email.length > 0 && validFormat && emailAvailable
+ }
- const checkIfIsEmail = (email) => {
- return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
- }
+ const checkIfIsEmail = (email) => {
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
+ }
- const submit = () => {
- if (!isValid()) return
+ const submit = () => {
+ if (!isValid()) {
+ return false
+ }
- props.onPressEnter()
- }
+ props.onPressEnter()
+ }
- const handleUpdate = (e) => {
- setEmail(e.target.value)
- }
+ const handleUpdate = (e) => {
+ setEmail(e.target.value)
+ }
- React.useEffect(() => {
- if (email.length === 0) {
- setEmailAvailable(null)
- setValidFormat(null)
+ React.useEffect(() => {
+ if (email.length === 0) {
+ setEmailAvailable(null)
+ setValidFormat(null)
- return
- }
+ return
+ }
- props.updateValue(null)
+ props.updateValue(null)
- setLoading(true)
+ setLoading(true)
- setValidFormat(checkIfIsEmail(email))
+ const isEmailValid = checkIfIsEmail(email)
+ setValidFormat(isEmailValid)
- // check if email is available
- const timer = setTimeout(async () => {
- if (!validFormat) return
+ // check if email is available
+ const timer = setTimeout(async () => {
+ if (!isEmailValid) {
+ return false
+ }
- const request = await AuthModel.availability({ email }).catch((error) => {
- antd.message.error(`Cannot check email availability: ${error.message}`)
+ const request = await AuthModel.availability({ email }).catch((error) => {
+ antd.message.error(`Cannot check email availability: ${error.message}`)
- return false
- })
+ return false
+ })
- if (request) {
- setEmailAvailable(!request.exist)
+ if (request) {
+ setEmailAvailable(!request.exist)
- if (request.exist) {
- antd.message.error("Email is already in use")
- props.updateValue(null)
- } else {
- props.updateValue(email)
- }
- }
+ if (request.exist) {
+ antd.message.error("Email is already in use")
+ props.updateValue(null)
+ } else {
+ props.updateValue(email)
+ }
+ }
- setLoading(false)
- }, 1000)
+ setLoading(false)
+ }, 1000)
- return () => clearTimeout(timer)
- }, [email])
+ return () => clearTimeout(timer)
+ }, [email])
- return
+ return (
+
+ )
}
export default {
- key: "email",
- title: "Step 3",
- icon: "FiMail",
- description: "Enter a email for the account, it can be used to access to your account. \n Will not be shared with anyone else and not be used for marketing purposes.",
- required: true,
- content: EmailStepComponent,
-}
\ No newline at end of file
+ key: "email",
+ title: "Step 3",
+ icon: "FiMail",
+ description:
+ "Enter a email for the account, it can be used to access to your account. \n Will not be shared with anyone else and not be used for marketing purposes.",
+ required: true,
+ content: EmailStepComponent,
+}
diff --git a/packages/app/src/pages/auth/forms/register/steps/tos/index.jsx b/packages/app/src/pages/auth/forms/register/steps/tos/index.jsx
index b361e493..0c14a226 100755
--- a/packages/app/src/pages/auth/forms/register/steps/tos/index.jsx
+++ b/packages/app/src/pages/auth/forms/register/steps/tos/index.jsx
@@ -1,80 +1,81 @@
-import React from "react"
import * as antd from "antd"
import MarkdownReader from "@components/MarkdownReader"
import config from "@config"
const FrameStyle = {
- "width": "60vw",
- "max-width": "60vw",
- "height": "90vh",
- "max-height": "90vh",
- "overflow": "overlay",
- "justify-content": "flex-start",
+ width: "60vw",
+ "max-width": "60vw",
+ height: "90vh",
+ "max-height": "90vh",
+ overflow: "overlay",
+ "justify-content": "flex-start",
}
const LegalDocumentsDecorators = {
- "terms": "Terms of Service",
- "privacy": "Privacy Policy",
+ terms: "Terms of Service",
+ privacy: "Privacy Policy",
}
function composeConfirmationCheckboxLabel(documents) {
- let labels = [
- "I have read and accept"
- ]
+ let labels = ["I have read and accept"]
- documents.forEach(([key, value], index) => {
- const isLast = index === documents.length - 1
+ documents.forEach(([key, value], index) => {
+ const isLast = index === documents.length - 1
- labels.push(`the ${LegalDocumentsDecorators[key] ?? `document (${key})`} ${!isLast ? "and" : ""}`)
- })
+ labels.push(
+ `the ${LegalDocumentsDecorators[key] ?? `document (${key})`} ${!isLast ? "and" : ""}`,
+ )
+ })
- return labels.join(" ")
+ return labels.join(" ")
}
const TermsOfServiceStepComponent = (props) => {
- const legalDocuments = Object.entries(config.legal)
+ const legalDocuments = Object.entries(config.legal)
- return
- {
- Object.entries(config.legal).map(([key, value]) => {
- if (!value) {
- return null
- }
+ return (
+
+ {Object.entries(config.legal).map(([key, value]) => {
+ if (!value) {
+ return null
+ }
- return
{
- app.layout.modal.open(key, MarkdownReader, {
- includeCloseButton: true,
- frameContentStyle: FrameStyle,
- props: {
- url: value
- }
- })
- }}
- >
- Read {LegalDocumentsDecorators[key] ?? `document (${key})`}
-
- })
- }
+ return (
+
{
+ app.layout.modal.open(key, MarkdownReader, {
+ includeCloseButton: true,
+ frameContentStyle: FrameStyle,
+ props: {
+ url: value,
+ },
+ })
+ }}
+ >
+ Read {LegalDocumentsDecorators[key] ?? `document (${key})`}
+
+ )
+ })}
-
{
- props.updateValue(event.target.checked)
- }}
- >
- {composeConfirmationCheckboxLabel(legalDocuments)}
-
-
+
{
+ props.updateValue(event.target.checked)
+ }}
+ >
+ {composeConfirmationCheckboxLabel(legalDocuments)}
+
+
+ )
}
export default {
- key: "tos",
- title: "Step 3",
- icon: "FileDone",
- description: "Take your time to read these legal documents.",
- required: true,
- content: TermsOfServiceStepComponent,
+ key: "tos",
+ title: "Step 3",
+ icon: "FileDone",
+ description: "Take your time to read these legal documents.",
+ required: true,
+ content: TermsOfServiceStepComponent,
}
diff --git a/packages/app/src/pages/auth/forms/selector/index.jsx b/packages/app/src/pages/auth/forms/selector/index.jsx
index ebc27c96..e9cb927e 100755
--- a/packages/app/src/pages/auth/forms/selector/index.jsx
+++ b/packages/app/src/pages/auth/forms/selector/index.jsx
@@ -6,9 +6,14 @@ import { Icons } from "@components/Icons"
const MainSelector = (props) => {
return (
<>
-
-

-
+ {!app.isMobile && (
+
+

+
+ )}
{app.userData && (
diff --git a/packages/app/src/pages/auth/index.mobile.jsx b/packages/app/src/pages/auth/index.mobile.jsx
index 3969df7e..bbf0f509 100755
--- a/packages/app/src/pages/auth/index.mobile.jsx
+++ b/packages/app/src/pages/auth/index.mobile.jsx
@@ -1,32 +1,55 @@
import React from "react"
+import config from "@config"
import useRandomFeaturedWallpaperUrl from "@hooks/useRandomFeaturedWallpaperUrl"
+import useUrlQueryActiveKey from "@hooks/useUrlQueryActiveKey"
+
+import RegisterForm from "./forms/register"
+import MainSelector from "./forms/selector"
+import RecoveryForm from "./forms/recovery"
+
+const keyToComponents = {
+ selector: MainSelector,
+ register: RegisterForm,
+ recovery: RecoveryForm,
+}
import "./index.mobile.less"
-export default (props) => {
+const AuthPage = (props) => {
const randomWallpaperURL = useRandomFeaturedWallpaperUrl()
-
- React.useEffect(() => {
- if (app.userData) {
- app.navigation.goMain()
- } else {
- app.auth.login()
- }
- }, [])
+ const [activeKey, setActiveKey] = useUrlQueryActiveKey({
+ defaultKey: "selector",
+ })
return (
-
+
- {/*
- {wallpaperData?.author ? wallpaperData.author : null}
-
*/}
+ />
+
+
+
+

+
+
+
+ {React.createElement(
+ keyToComponents[activeKey] ?? keyToComponents["selector"],
+ {
+ setActiveKey: setActiveKey,
+ },
+ )}
+
)
}
+
+export default AuthPage
diff --git a/packages/app/src/pages/auth/index.mobile.less b/packages/app/src/pages/auth/index.mobile.less
index b05a5d18..ea485231 100755
--- a/packages/app/src/pages/auth/index.mobile.less
+++ b/packages/app/src/pages/auth/index.mobile.less
@@ -1,22 +1,89 @@
-.loginPage {
- position: relative;
+.login-page {
+ position: relative;
- width: 100%;
- height: 100vh;
- height: 100dvh;
+ display: flex;
+ flex-direction: column;
- .wallpaper {
- position: absolute;
+ align-items: center;
+ justify-content: center;
- top: 0;
- left: 0;
+ width: 100%;
+ height: 100vh;
+ height: 100dvh;
- width: 100%;
- height: 100vh;
- height: 100dvh;
+ .wallpaper {
+ position: fixed;
- background-position: center;
- background-size: cover;
- background-repeat: no-repeat;
- }
-}
\ No newline at end of file
+ top: 0;
+ left: 0;
+
+ z-index: -1;
+
+ width: 100%;
+ height: 100vh;
+ height: 100dvh;
+
+ background-position: center;
+ background-size: cover;
+ background-repeat: no-repeat;
+ }
+
+ .login-page-card {
+ position: relative;
+
+ display: flex;
+ flex-direction: column;
+
+ width: 95%;
+ padding: 15px;
+
+ background-color: var(--background-color-primary);
+
+ border-radius: 24px;
+
+ gap: 30px;
+
+ &__header {
+ display: flex;
+ flex-direction: row;
+
+ align-items: center;
+
+ gap: 10px;
+
+ &__logo {
+ width: 40px;
+ height: 40px;
+ }
+ }
+
+ &__content {
+ display: flex;
+ flex-direction: column;
+
+ .ant-btn {
+ height: fit-content;
+ padding: 7px 15px;
+ font-size: 0.8rem;
+ }
+
+ .actions {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+
+ gap: 15px;
+ }
+
+ .register_form {
+ position: unset;
+
+ .register_form_actions {
+ top: 0;
+ bottom: unset;
+ }
+ }
+ }
+ }
+}
diff --git a/packages/app/src/pages/lyrics/components/text/index.jsx b/packages/app/src/pages/lyrics/components/text/index.jsx
index 3fe85416..a2b3e3fd 100644
--- a/packages/app/src/pages/lyrics/components/text/index.jsx
+++ b/packages/app/src/pages/lyrics/components/text/index.jsx
@@ -104,6 +104,8 @@ const LyricsText = React.forwardRef((props, textRef) => {
React.useEffect(() => {
setVisible(false)
setCurrentLineIndex(0)
+ // set scroll top to 0
+ textRef.current.scrollTop = 0
}, [playerState.track_manifest])
React.useEffect(() => {
diff --git a/packages/server/services/auth/auth.service.js b/packages/server/services/auth/auth.service.js
index 1cb3f8aa..b0e008f8 100644
--- a/packages/server/services/auth/auth.service.js
+++ b/packages/server/services/auth/auth.service.js
@@ -35,7 +35,6 @@ export default class API extends Server {
onExit() {
this.queuesManager.cleanUp()
- console.log("Jijija")
}
}
diff --git a/packages/server/services/auth/classes/account/methods/create.js b/packages/server/services/auth/classes/account/methods/create.js
index d64c280a..63478c75 100644
--- a/packages/server/services/auth/classes/account/methods/create.js
+++ b/packages/server/services/auth/classes/account/methods/create.js
@@ -1,60 +1,84 @@
import bcrypt from "bcrypt"
import { User } from "@db_models"
-import requiredFields from "@shared-utils/requiredFields"
-
import Account from "@classes/account"
+import requiredFields from "@shared-utils/requiredFields"
+import verifyTurnstileToken from "@utils/verifyTurnstileToken"
+
export default async (payload) => {
- requiredFields(["username", "password", "email"], payload)
+ requiredFields(["username", "password", "email"], payload)
- let { username, password, email, public_name, roles, avatar, accept_tos } = payload
+ let {
+ username,
+ password,
+ email,
+ public_name,
+ roles,
+ avatar,
+ accept_tos,
+ captcha,
+ } = payload
- if (ToBoolean(accept_tos) !== true) {
- throw new OperationError(400, "You must accept the terms of service in order to create an account.")
- }
+ if (ToBoolean(accept_tos) !== true) {
+ throw new OperationError(
+ 400,
+ "You must accept the terms of service in order to create an account.",
+ )
+ }
- await Account.usernameMeetPolicy(username)
+ if (!captcha) {
+ throw new OperationError(400, "Captcha token is required")
+ }
- // check if username is already taken
- const existentUser = await User
- .findOne({ username: username })
+ const turnstileResponse = await verifyTurnstileToken(captcha)
- if (existentUser) {
- throw new OperationError(400, "User already exists")
- }
+ if (turnstileResponse.success !== true) {
+ throw new OperationError(400, "Invalid captcha token")
+ }
- // check if the email is already in use
- const existentEmail = await User
- .findOne({ email: email })
- .select("+email")
+ await Account.usernameMeetPolicy(username)
- if (existentEmail) {
- throw new OperationError(400, "Email already in use")
- }
+ // check if username is already taken
+ const existentUser = await User.findOne({ username: username })
- await Account.passwordMeetPolicy(password)
+ if (existentUser) {
+ throw new OperationError(400, "User already exists")
+ }
- // hash the password
- const hash = bcrypt.hashSync(password, parseInt(process.env.BCRYPT_ROUNDS ?? 3))
+ // check if the email is already in use
+ const existentEmail = await User.findOne({ email: email }).select("+email")
- let user = new User({
- username: username,
- password: hash,
- email: email,
- public_name: public_name,
- avatar: avatar ?? `https://api.dicebear.com/7.x/thumbs/svg?seed=${username}`,
- roles: roles,
- created_at: new Date().getTime(),
- accept_tos: accept_tos,
- activated: false,
- })
+ if (existentEmail) {
+ throw new OperationError(400, "Email already in use")
+ }
- await user.save()
+ await Account.passwordMeetPolicy(password)
- await Account.sendActivationCode(user._id.toString())
+ // hash the password
+ const hash = bcrypt.hashSync(
+ password,
+ parseInt(process.env.BCRYPT_ROUNDS ?? 3),
+ )
- return {
- activation_required: true,
- user: user,
- }
-}
\ No newline at end of file
+ let user = new User({
+ username: username,
+ password: hash,
+ email: email,
+ public_name: public_name,
+ avatar:
+ avatar ?? `https://api.dicebear.com/7.x/thumbs/svg?seed=${username}`,
+ roles: roles,
+ created_at: new Date().getTime(),
+ accept_tos: accept_tos,
+ activated: false,
+ })
+
+ await user.save()
+
+ await Account.sendActivationCode(user._id.toString())
+
+ return {
+ activation_required: true,
+ user: user,
+ }
+}
diff --git a/packages/server/services/auth/utils/verifyTurnstileToken.js b/packages/server/services/auth/utils/verifyTurnstileToken.js
new file mode 100644
index 00000000..693c52c1
--- /dev/null
+++ b/packages/server/services/auth/utils/verifyTurnstileToken.js
@@ -0,0 +1,26 @@
+import axios from "axios"
+
+export default async (token) => {
+ const secret = process.env.TURNSTILE_SECRET
+
+ if (!secret) {
+ throw new Error("Turnstile secret is not set")
+ }
+
+ let response = await axios({
+ url: "https://challenges.cloudflare.com/turnstile/v0/siteverify",
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ data: {
+ secret: secret,
+ response: token,
+ },
+ }).catch((err) => {
+ console.error(err.response.data)
+ throw new Error("Turnstile verification failed")
+ })
+
+ return response.data
+}