Merged in feature/sas-mypages (pull request #1302)
Feature/sas mypages * feat: Add SAS partner page under my pages * fix: feature toggle SAS Partner page in my pages * add translations for SAS account page * use 'flex-start' instead of 'start' * fix: flatten css * fix: don't use <SectionContainer /> on linkedAccounts page
This commit is contained in:
@@ -35,7 +35,8 @@ export default function CardsGrid({
|
||||
<SectionHeader
|
||||
title={cards_grid.title}
|
||||
preamble={cards_grid.preamble}
|
||||
topTitle={firstItem}
|
||||
headingAs={firstItem ? "h3" : "h4"}
|
||||
headingLevel={firstItem ? "h1" : "h2"}
|
||||
/>
|
||||
<Grids.Stackable columns={columns}>
|
||||
{cards_grid.cards.map((card, index) => {
|
||||
|
||||
@@ -26,7 +26,13 @@ export default async function Overview({
|
||||
|
||||
return (
|
||||
<SectionContainer>
|
||||
<SectionHeader link={link} preamble={subtitle} title={title} topTitle />
|
||||
<SectionHeader
|
||||
link={link}
|
||||
preamble={subtitle}
|
||||
title={title}
|
||||
headingAs={"h3"}
|
||||
headingLevel={"h1"}
|
||||
/>
|
||||
<Hero color="red">
|
||||
<Friend membership={user.membership} name={user.name}>
|
||||
<MembershipNumber color="burgundy" membership={user.membership} />
|
||||
|
||||
@@ -26,7 +26,13 @@ export default async function PointsOverview({
|
||||
|
||||
return (
|
||||
<SectionContainer>
|
||||
<SectionHeader link={link} preamble={subtitle} title={title} topTitle />
|
||||
<SectionHeader
|
||||
link={link}
|
||||
preamble={subtitle}
|
||||
title={title}
|
||||
headingAs={"h3"}
|
||||
headingLevel={"h1"}
|
||||
/>
|
||||
<Hero color="burgundy">
|
||||
<Friend membership={user.membership} name={user.name}>
|
||||
<MembershipNumber color="red" membership={user.membership} />
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import Image from "next/image"
|
||||
|
||||
import RocketLaunch from "@/components/Icons/RocketLaunch"
|
||||
import SkeletonShimmer from "@/components/SkeletonShimmer"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
import { getIntl } from "@/i18n"
|
||||
|
||||
import styles from "./tierLevelCard.module.css"
|
||||
|
||||
import type { ComponentProps, ReactNode } from "react"
|
||||
|
||||
type BoostState = "boostedInOtherSystem" | "boostedInThisSystem" | "notBoosted"
|
||||
|
||||
type BaseProps = {
|
||||
points: number
|
||||
tier: string
|
||||
boostState: BoostState
|
||||
}
|
||||
|
||||
type BoostedInOther = BaseProps & {
|
||||
boostState: "boostedInOtherSystem"
|
||||
boostedTier: string
|
||||
boostExpiration: Date
|
||||
}
|
||||
|
||||
type BoostedInThis = BaseProps & {
|
||||
boostState: "boostedInThisSystem"
|
||||
}
|
||||
|
||||
type NotBoosted = BaseProps & {
|
||||
boostState: "notBoosted"
|
||||
}
|
||||
|
||||
const variants = cva(styles.tierlevelcard, {
|
||||
variants: {
|
||||
bonusSystem: {
|
||||
scandic: styles.scandic,
|
||||
sas: styles.sas,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
type Props = VariantProps<typeof variants> &
|
||||
(BoostedInOther | BoostedInThis | NotBoosted)
|
||||
export async function TierLevelCard({
|
||||
points,
|
||||
tier,
|
||||
bonusSystem,
|
||||
...boosted
|
||||
}: Props) {
|
||||
const intl = await getIntl()
|
||||
|
||||
const className = variants({ bonusSystem })
|
||||
|
||||
return (
|
||||
<article className={className}>
|
||||
{boosted.boostState === "boostedInOtherSystem" && (
|
||||
<section className={styles.boostedInfo}>
|
||||
<div className={styles.boostedTier}>
|
||||
<Caption
|
||||
uppercase
|
||||
type="bold"
|
||||
color={bonusSystemSpecifics[bonusSystem!].color}
|
||||
>
|
||||
{boosted.boostedTier}
|
||||
</Caption>
|
||||
<Caption
|
||||
type="bold"
|
||||
color={bonusSystemSpecifics[bonusSystem!].color}
|
||||
>
|
||||
{intl.formatMessage({ id: "Level upgrade" })}
|
||||
</Caption>
|
||||
</div>
|
||||
<Caption color={bonusSystemSpecifics[bonusSystem!].color}>
|
||||
{intl.formatMessage(
|
||||
{
|
||||
id: "Upgrade expires {upgradeExpires, date, short}",
|
||||
},
|
||||
{ upgradeExpires: boosted.boostExpiration }
|
||||
)}
|
||||
</Caption>
|
||||
</section>
|
||||
)}
|
||||
<section className={styles.baseInfo}>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.tierInfo}>
|
||||
<span>
|
||||
<Caption
|
||||
uppercase
|
||||
type="bold"
|
||||
color={bonusSystemSpecifics[bonusSystem!].color}
|
||||
>
|
||||
{tier}
|
||||
</Caption>
|
||||
</span>
|
||||
<div className={styles.logoContainer}>
|
||||
{bonusSystemSpecifics[bonusSystem!].logo}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{boosted.boostState === "boostedInThisSystem" && (
|
||||
<Footnote className={styles.footnote}>
|
||||
<RocketLaunch
|
||||
color={bonusSystemSpecifics[bonusSystem!].rocketLaunchColor}
|
||||
/>
|
||||
<span>
|
||||
{intl.formatMessage({
|
||||
id: bonusSystemSpecifics[bonusSystem!].boostTextId,
|
||||
})}
|
||||
</span>
|
||||
</Footnote>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Subtitle>
|
||||
{intl.formatMessage(
|
||||
{ id: "{points, number} Bonus points" },
|
||||
{ points }
|
||||
)}
|
||||
</Subtitle>
|
||||
</section>
|
||||
</article>
|
||||
)
|
||||
}
|
||||
|
||||
export function TierLevelCardSkeleton({
|
||||
bonusSystem,
|
||||
}: {
|
||||
bonusSystem?: NonNullable<Props["bonusSystem"]>
|
||||
}) {
|
||||
const className = variants({ bonusSystem })
|
||||
|
||||
return (
|
||||
<article className={className}>
|
||||
<section className={styles.baseInfo}>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.tierInfo}>
|
||||
<span>
|
||||
<SkeletonShimmer width={"50px"} height={"16px"} />
|
||||
</span>
|
||||
|
||||
{bonusSystem && (
|
||||
<div className={styles.eurobonusLogo}>
|
||||
{bonusSystemSpecifics[bonusSystem!].logo}
|
||||
</div>
|
||||
)}
|
||||
{!bonusSystem && <SkeletonShimmer width={"74px"} height={"16px"} />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Subtitle>
|
||||
<SkeletonShimmer width={"240px"} height={"26px"} />
|
||||
</Subtitle>
|
||||
</section>
|
||||
</article>
|
||||
)
|
||||
}
|
||||
|
||||
type BonusSystemSpecifics = {
|
||||
boostTextId: string
|
||||
logo: ReactNode
|
||||
rocketLaunchColor: ComponentProps<typeof RocketLaunch>["color"]
|
||||
color: ComponentProps<typeof Caption>["color"]
|
||||
}
|
||||
|
||||
type BonusSystemMapping = {
|
||||
[bonusSystem in NonNullable<
|
||||
VariantProps<typeof variants>["bonusSystem"]
|
||||
>]: BonusSystemSpecifics
|
||||
}
|
||||
|
||||
const bonusSystemSpecifics: BonusSystemMapping = {
|
||||
scandic: {
|
||||
boostTextId: "Your Friends level has upgraded your Eurobonus level",
|
||||
rocketLaunchColor: "red",
|
||||
color: "burgundy",
|
||||
logo: (
|
||||
<Image
|
||||
alt="Scandic logo"
|
||||
height={16}
|
||||
src="/_static/img/scandic-logotype.svg"
|
||||
width={74}
|
||||
/>
|
||||
),
|
||||
},
|
||||
sas: {
|
||||
boostTextId: "Your Eurobonus level has upgraded your friends level",
|
||||
rocketLaunchColor: "blue",
|
||||
color: "uiTextActive",
|
||||
logo: (
|
||||
<>
|
||||
<Image
|
||||
alt="SAS logo"
|
||||
height={16}
|
||||
src="/_static/img/sas/sas-logotype.svg"
|
||||
width={44}
|
||||
/>
|
||||
<Caption uppercase type="bold">
|
||||
Eurobonus
|
||||
</Caption>
|
||||
</>
|
||||
),
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
.tierlevelcard {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
justify-content: space-between;
|
||||
|
||||
border-radius: var(--Corner-radius-Medium);
|
||||
background: white;
|
||||
box-shadow: 0px 0px 8px 3px #0000001a;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
width: 100%;
|
||||
min-height: 176px;
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
max-width: 335px;
|
||||
}
|
||||
|
||||
&.scandic {
|
||||
background: white;
|
||||
color: var(--Main-Brand-Burgundy);
|
||||
|
||||
.boostedInfo {
|
||||
background: linear-gradient(86.64deg, #faf6f2 0%, #f4d5c8 100.91%);
|
||||
border-radius: var(--Corner-radius-Medium);
|
||||
}
|
||||
}
|
||||
|
||||
&.sas {
|
||||
background: #f0f4ff;
|
||||
color: var(--Main-Brand-DarkBlue);
|
||||
|
||||
.boostedInfo {
|
||||
background: linear-gradient(90deg, #f0f4ff 0%, #bdcdff 100%);
|
||||
border-radius: var(--Corner-radius-Medium);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.boostedInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x-half);
|
||||
box-shadow: 0px 0px 4px 2px #0000001a;
|
||||
|
||||
flex: 0;
|
||||
padding: var(--Spacing-x2) var(--Spacing-x-one-and-half);
|
||||
}
|
||||
|
||||
.boostedTier {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: var(--Spacing-x-one-and-half);
|
||||
}
|
||||
|
||||
.baseInfo {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x-one-and-half);
|
||||
|
||||
padding: var(--Spacing-x2) var(--Spacing-x-one-and-half);
|
||||
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.tierInfo {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
text-transform: uppercase;
|
||||
gap: var(--Spacing-x1);
|
||||
}
|
||||
|
||||
.logoContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: var(--Spacing-x1);
|
||||
}
|
||||
|
||||
.footnote {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: var(--Spacing-x1);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
"use client"
|
||||
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { trpc } from "@/lib/trpc/client"
|
||||
|
||||
import Refresh from "@/components/Icons/Refresh"
|
||||
import { Loading } from "@/components/Loading"
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
import { toast } from "@/components/TempDesignSystem/Toasts"
|
||||
|
||||
import styles from "./levelupgradebutton.module.css"
|
||||
|
||||
export function LevelUpgradeButton() {
|
||||
const intl = useIntl()
|
||||
|
||||
const { mutate, isPending } =
|
||||
trpc.partner.sas.performLevelUpgrade.useMutation({
|
||||
onSuccess() {
|
||||
toast.success(intl.formatMessage({ id: "Level upgraded" }))
|
||||
},
|
||||
onError() {
|
||||
toast.error(intl.formatMessage({ id: "Failed to upgrade level" }))
|
||||
},
|
||||
})
|
||||
|
||||
const handleClick = () => {
|
||||
mutate()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
intent="primary"
|
||||
theme="primaryLight"
|
||||
onClick={handleClick}
|
||||
className={styles.button}
|
||||
>
|
||||
<div
|
||||
className={styles.textContainer}
|
||||
style={{ visibility: isPending ? "hidden" : "visible" }}
|
||||
>
|
||||
<Refresh color="currentColor" />
|
||||
{intl.formatMessage({ id: "Check for level upgrade" })}
|
||||
</div>
|
||||
{isPending && <Loading color="white" className={styles.loading} />}
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
"use client"
|
||||
|
||||
import { useRouter } from "next/navigation"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { trpc } from "@/lib/trpc/client"
|
||||
|
||||
import { Loading } from "@/components/Loading"
|
||||
import Link from "@/components/TempDesignSystem/Link"
|
||||
import { toast } from "@/components/TempDesignSystem/Toasts"
|
||||
|
||||
export function UnlinkSAS() {
|
||||
const intl = useIntl()
|
||||
const router = useRouter()
|
||||
|
||||
const { mutate, isPending } = trpc.partner.sas.unlinkAccount.useMutation({
|
||||
onSuccess() {
|
||||
toast.success(intl.formatMessage({ id: "Account unlinked, reloading" }))
|
||||
// TODO: reload page
|
||||
router.push("/en/scandic-friends/my-pages")
|
||||
},
|
||||
onError() {
|
||||
toast.error(intl.formatMessage({ id: "Failed to unlink account" }))
|
||||
},
|
||||
})
|
||||
|
||||
const handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
|
||||
event.preventDefault()
|
||||
mutate()
|
||||
}
|
||||
|
||||
if (isPending) {
|
||||
return <Loading color="burgundy" />
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
href="#"
|
||||
onClick={handleClick}
|
||||
color="burgundy"
|
||||
variant="default"
|
||||
weight="bold"
|
||||
>
|
||||
{intl.formatMessage({ id: "Unlink accounts" })}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import { Suspense } from "react"
|
||||
import { setTimeout } from "timers/promises"
|
||||
|
||||
import { TIER_TO_FRIEND_MAP } from "@/constants/membershipLevels"
|
||||
import { env } from "@/env/server"
|
||||
import { getProfile } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import SectionContainer from "@/components/Section/Container"
|
||||
import SectionHeader from "@/components/Section/Header"
|
||||
import SectionLink from "@/components/Section/Link"
|
||||
|
||||
import { TierLevelCard, TierLevelCardSkeleton } from "./Card/TierLevelCard"
|
||||
import { LevelUpgradeButton } from "./LevelUpgradeButton"
|
||||
import { UnlinkSAS } from "./UnlinkSAS"
|
||||
|
||||
import styles from "./linkedAccounts.module.css"
|
||||
|
||||
type Props = {
|
||||
title?: string
|
||||
link?: { href: string; text: string }
|
||||
subtitle?: string
|
||||
}
|
||||
|
||||
export default async function SASLinkedAccount({
|
||||
title,
|
||||
subtitle,
|
||||
link,
|
||||
}: Props) {
|
||||
if (!env.SAS_ENABLED) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<SectionContainer>
|
||||
<SectionHeader link={link} preamble={subtitle} title={title} />
|
||||
<SectionLink link={link} variant="mobile" />
|
||||
<section className={styles.cardsContainer}>
|
||||
<Suspense fallback={<TierLevelCardsSkeleton />}>
|
||||
<TierLevelCards />
|
||||
</Suspense>
|
||||
</section>
|
||||
</SectionContainer>
|
||||
<div className={styles.mutationSection}>
|
||||
<LevelUpgradeButton />
|
||||
<UnlinkSAS />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function TierLevelCardsSkeleton() {
|
||||
return (
|
||||
<>
|
||||
<TierLevelCardSkeleton bonusSystem={"scandic"} />
|
||||
<TierLevelCardSkeleton bonusSystem={"sas"} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
async function TierLevelCards() {
|
||||
console.log("[SAS] Fetching tier level cards")
|
||||
await setTimeout(2_000)
|
||||
console.log("[SAS] AFTER Fetching tier level cards")
|
||||
|
||||
const user = await getProfile()
|
||||
if (!user || "error" in user) {
|
||||
return null
|
||||
}
|
||||
|
||||
const sasPoints = 250_000
|
||||
const sfPoints = user.membership?.currentPoints || 0
|
||||
const sfLevelName =
|
||||
TIER_TO_FRIEND_MAP[user.membership?.membershipLevel ?? "L1"]
|
||||
|
||||
return (
|
||||
<>
|
||||
<TierLevelCard
|
||||
points={sfPoints}
|
||||
tier={sfLevelName}
|
||||
boostState="boostedInThisSystem"
|
||||
bonusSystem={"scandic"}
|
||||
/>
|
||||
<TierLevelCard
|
||||
points={sasPoints}
|
||||
tier="Silver"
|
||||
boostState="boostedInOtherSystem"
|
||||
bonusSystem={"sas"}
|
||||
boostExpiration={new Date("2022-12-31")}
|
||||
boostedTier="Gold"
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
.button {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
& .textContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: var(--Spacing-x1);
|
||||
}
|
||||
}
|
||||
|
||||
.loading {
|
||||
position: absolute;
|
||||
&.hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
.divider {
|
||||
margin-top: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.divider {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.cardsContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x3);
|
||||
justify-content: flex-start;
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
.mutationSection {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x2);
|
||||
align-items: center;
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,8 @@ export default function SectionWrapper({
|
||||
link={dynamic_content.link}
|
||||
preamble={dynamic_content.subtitle}
|
||||
title={dynamic_content.title}
|
||||
topTitle={firstItem}
|
||||
headingAs={firstItem ? "h3" : "h4"}
|
||||
headingLevel={firstItem ? "h1" : "h2"}
|
||||
/>
|
||||
) : null}
|
||||
{children}
|
||||
|
||||
@@ -11,6 +11,7 @@ import ExpiringPoints from "@/components/Blocks/DynamicContent/Points/ExpiringPo
|
||||
import PointsOverview from "@/components/Blocks/DynamicContent/Points/Overview"
|
||||
import CurrentRewardsBlock from "@/components/Blocks/DynamicContent/Rewards/CurrentRewards"
|
||||
import NextLevelRewardsBlock from "@/components/Blocks/DynamicContent/Rewards/NextLevel"
|
||||
import SASLinkedAccount from "@/components/Blocks/DynamicContent/SAS/LinkedAccounts"
|
||||
import SASTierComparisonBlock from "@/components/Blocks/DynamicContent/SASTierComparison"
|
||||
import SignupFormWrapper from "@/components/Blocks/DynamicContent/SignupFormWrapper"
|
||||
import SignUpVerification from "@/components/Blocks/DynamicContent/SignUpVerification"
|
||||
@@ -75,6 +76,8 @@ function DynamicContentBlocks(props: DynamicContentProps) {
|
||||
return <SoonestStays {...dynamic_content} />
|
||||
case DynamicContentEnum.Blocks.components.upcoming_stays:
|
||||
return <UpcomingStays {...dynamic_content} />
|
||||
case DynamicContentEnum.Blocks.components.sas_linked_account:
|
||||
return <SASLinkedAccount {...dynamic_content} />
|
||||
case DynamicContentEnum.Blocks.components.sas_tier_comparison:
|
||||
return (
|
||||
<SASTierComparisonBlock
|
||||
|
||||
@@ -36,7 +36,12 @@ export default function ShortcutsList({
|
||||
|
||||
return (
|
||||
<SectionContainer>
|
||||
<SectionHeader preamble={subtitle} title={title} topTitle={firstItem} />
|
||||
<SectionHeader
|
||||
preamble={subtitle}
|
||||
title={title}
|
||||
headingAs={firstItem ? "h3" : "h4"}
|
||||
headingLevel={firstItem ? "h1" : "h2"}
|
||||
/>
|
||||
<section className={styles.section}>
|
||||
{columns.map(({ id, column }) => (
|
||||
<ShortcutsListItems
|
||||
|
||||
27
components/Icons/Refresh.tsx
Normal file
27
components/Icons/Refresh.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { iconVariants } from "./variants"
|
||||
|
||||
import type { IconProps } from "@/types/components/icon"
|
||||
|
||||
export default function RocketLaunch({
|
||||
className,
|
||||
color,
|
||||
...props
|
||||
}: IconProps) {
|
||||
const classNames = iconVariants({ className, color })
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={classNames}
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M6.0249 12.0254C6.0249 12.2921 6.04157 12.5546 6.0749 12.8129C6.10824 13.0712 6.16657 13.3296 6.2499 13.5879C6.3249 13.8462 6.31657 14.0962 6.2249 14.3379C6.13324 14.5796 5.96657 14.7546 5.7249 14.8629C5.4749 14.9796 5.23115 14.9858 4.99365 14.8816C4.75615 14.7775 4.59574 14.5962 4.5124 14.3379C4.3874 13.9629 4.29574 13.5816 4.2374 13.1941C4.17907 12.8066 4.1499 12.4171 4.1499 12.0254C4.1499 9.83372 4.91032 7.97331 6.43115 6.44414C7.95199 4.91497 9.81657 4.15039 12.0249 4.15039H12.2749L11.3124 3.18789C11.1291 3.00456 11.0374 2.78372 11.0374 2.52539C11.0374 2.26706 11.1291 2.04622 11.3124 1.86289C11.4957 1.67956 11.7166 1.58789 11.9749 1.58789C12.2332 1.58789 12.4541 1.67956 12.6374 1.86289L15.1999 4.43789C15.3916 4.62956 15.4853 4.85039 15.4812 5.10039C15.477 5.35039 15.3791 5.57122 15.1874 5.76289L12.6374 8.31289C12.4541 8.49622 12.2332 8.58789 11.9749 8.58789C11.7166 8.58789 11.4957 8.49622 11.3124 8.31289C11.1291 8.12956 11.0374 7.90872 11.0374 7.65039C11.0374 7.39206 11.1291 7.17122 11.3124 6.98789L12.2749 6.02539H12.0249C10.3499 6.02539 8.93115 6.60664 7.76865 7.76914C6.60615 8.93164 6.0249 10.3504 6.0249 12.0254ZM17.9749 11.9254C17.9749 11.6587 17.9582 11.4004 17.9249 11.1504C17.8916 10.9004 17.8374 10.6504 17.7624 10.4004C17.6874 10.1421 17.6957 9.88789 17.7874 9.63789C17.8791 9.38789 18.0457 9.20872 18.2874 9.10039C18.5374 8.98372 18.7791 8.97539 19.0124 9.07539C19.2457 9.17539 19.4041 9.35456 19.4874 9.61289C19.6124 9.98789 19.7041 10.3691 19.7624 10.7566C19.8207 11.1441 19.8499 11.5337 19.8499 11.9254C19.8499 14.1171 19.0895 15.9775 17.5687 17.5066C16.0478 19.0358 14.1832 19.8004 11.9749 19.8004H11.7249L12.6874 20.7629C12.8707 20.9462 12.9624 21.1671 12.9624 21.4254C12.9624 21.6837 12.8707 21.9046 12.6874 22.0879C12.5041 22.2712 12.2832 22.3629 12.0249 22.3629C11.7666 22.3629 11.5457 22.2712 11.3624 22.0879L8.7999 19.5129C8.60824 19.3212 8.51449 19.1004 8.51865 18.8504C8.52282 18.6004 8.62074 18.3796 8.8124 18.1879L11.3624 15.6379C11.5457 15.4546 11.7666 15.3629 12.0249 15.3629C12.2832 15.3629 12.5041 15.4546 12.6874 15.6379C12.8707 15.8212 12.9624 16.0421 12.9624 16.3004C12.9624 16.5587 12.8707 16.7796 12.6874 16.9629L11.7249 17.9254H11.9749C13.6499 17.9254 15.0687 17.3441 16.2312 16.1816C17.3937 15.0191 17.9749 13.6004 17.9749 11.9254Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
27
components/Icons/RocketLaunch.tsx
Normal file
27
components/Icons/RocketLaunch.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { iconVariants } from "./variants"
|
||||
|
||||
import type { IconProps } from "@/types/components/icon"
|
||||
|
||||
export default function RocketLaunch({
|
||||
className,
|
||||
color,
|
||||
...props
|
||||
}: IconProps) {
|
||||
const classNames = iconVariants({ className, color })
|
||||
return (
|
||||
<svg
|
||||
className={classNames}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M5.6498 10.0249L7.5998 10.8499C7.83314 10.3832 8.0748 9.93319 8.3248 9.49986C8.5748 9.06653 8.8498 8.63319 9.1498 8.19986L7.7498 7.92486L5.6498 10.0249ZM9.1998 12.0999L12.0498 14.9249C12.7498 14.6582 13.4998 14.2499 14.2998 13.6999C15.0998 13.1499 15.8498 12.5249 16.5498 11.8249C17.7165 10.6582 18.629 9.36236 19.2873 7.93736C19.9456 6.51236 20.2331 5.19986 20.1498 3.99986C18.9498 3.91653 17.6331 4.20403 16.1998 4.86236C14.7665 5.52069 13.4665 6.4332 12.2998 7.59986C11.5998 8.29986 10.9748 9.04986 10.4248 9.84986C9.8748 10.6499 9.46647 11.3999 9.1998 12.0999ZM13.6498 10.4749C13.2665 10.0915 13.0748 9.62069 13.0748 9.06236C13.0748 8.50403 13.2665 8.0332 13.6498 7.64986C14.0331 7.26653 14.5081 7.07486 15.0748 7.07486C15.6415 7.07486 16.1165 7.26653 16.4998 7.64986C16.8831 8.0332 17.0748 8.50403 17.0748 9.06236C17.0748 9.62069 16.8831 10.0915 16.4998 10.4749C16.1165 10.8582 15.6415 11.0499 15.0748 11.0499C14.5081 11.0499 14.0331 10.8582 13.6498 10.4749ZM14.1248 18.4999L16.2248 16.3999L15.9498 14.9999C15.5165 15.2999 15.0831 15.5707 14.6498 15.8124C14.2165 16.054 13.7665 16.2915 13.2998 16.5249L14.1248 18.4999ZM21.9498 2.17486C22.2665 4.19153 22.0706 6.15403 21.3623 8.06236C20.654 9.9707 19.4331 11.7915 17.6998 13.5249L18.1998 15.9999C18.2665 16.3332 18.2498 16.6582 18.1498 16.9749C18.0498 17.2915 17.8831 17.5665 17.6498 17.7999L13.4498 21.9999L11.3498 17.0749L7.0748 12.7999L2.1498 10.6999L6.3248 6.49986C6.55814 6.26653 6.83731 6.09986 7.1623 5.99986C7.4873 5.89986 7.81647 5.88319 8.1498 5.94986L10.6248 6.44986C12.3581 4.71653 14.1748 3.49153 16.0748 2.77486C17.9748 2.05819 19.9331 1.85819 21.9498 2.17486ZM3.9248 15.9749C4.50814 15.3915 5.22064 15.0957 6.0623 15.0874C6.90397 15.079 7.61647 15.3665 8.1998 15.9499C8.78314 16.5332 9.07064 17.2457 9.0623 18.0874C9.05397 18.929 8.75814 19.6415 8.1748 20.2249C7.75814 20.6415 7.0623 20.9999 6.0873 21.2999C5.1123 21.5999 3.76647 21.8665 2.0498 22.0999C2.28314 20.3832 2.5498 19.0374 2.8498 18.0624C3.1498 17.0874 3.50814 16.3915 3.9248 15.9749ZM5.3498 17.3749C5.18314 17.5415 5.01647 17.8457 4.8498 18.2874C4.68314 18.729 4.56647 19.1749 4.4998 19.6249C4.9498 19.5582 5.39564 19.4457 5.8373 19.2874C6.27897 19.129 6.58314 18.9665 6.7498 18.7999C6.9498 18.5999 7.05814 18.3582 7.0748 18.0749C7.09147 17.7915 6.9998 17.5499 6.7998 17.3499C6.5998 17.1499 6.35814 17.054 6.0748 17.0624C5.79147 17.0707 5.5498 17.1749 5.3498 17.3749Z"
|
||||
fill="#003DF0"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -96,3 +96,8 @@
|
||||
.disabled * {
|
||||
fill: var(--Base-Text-Disabled);
|
||||
}
|
||||
|
||||
.currentColor,
|
||||
.currentColor * {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ const config = {
|
||||
uiTextMediumContrast: styles.uiTextMediumContrast,
|
||||
uiTextPlaceholder: styles.uiTextPlaceholder,
|
||||
disabled: styles.disabled,
|
||||
currentColor: styles.currentColor,
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
|
||||
46
components/Loading/index.tsx
Normal file
46
components/Loading/index.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import styles from "./loading.module.css"
|
||||
|
||||
type Size = "small" | "medium" | "large"
|
||||
type Color = "white" | "burgundy"
|
||||
type Props = {
|
||||
size?: Size
|
||||
color?: Color
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function Loading({
|
||||
size = "medium",
|
||||
color = "burgundy",
|
||||
className,
|
||||
}: Props) {
|
||||
return (
|
||||
<svg
|
||||
width={sizes[size]}
|
||||
height={sizes[size]}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={`${styles.loading} ${className}`}
|
||||
>
|
||||
<circle cx="10" cy="1.73913" r="1.73913" fill={colors[color]} />
|
||||
<circle cx="16.087" cy="4.34783" r="1.73913" fill={colors[color]} />
|
||||
<circle cx="18.2609" cy="10" r="1.73913" fill={colors[color]} />
|
||||
<circle cx="16.087" cy="15.6522" r="1.73913" fill={colors[color]} />
|
||||
<circle cx="10" cy="18.2609" r="1.73913" fill={colors[color]} />
|
||||
<circle cx="3.91304" cy="15.6522" r="1.73913" fill={colors[color]} />
|
||||
<circle cx="1.73913" cy="10" r="1.73913" fill={colors[color]} />
|
||||
<circle cx="3.91304" cy="4.34783" r="1.73913" fill={colors[color]} />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
const colors: { [color in Color]: string } = {
|
||||
white: "#fff",
|
||||
burgundy: "var(--Scandic-Brand-Burgundy)",
|
||||
}
|
||||
|
||||
const sizes: { [size in Size]: number } = {
|
||||
small: 16,
|
||||
medium: 20,
|
||||
large: 24,
|
||||
}
|
||||
43
components/Loading/loading.module.css
Normal file
43
components/Loading/loading.module.css
Normal file
@@ -0,0 +1,43 @@
|
||||
.loading {
|
||||
--animationDuration: 0.8s;
|
||||
--delayPerItem: calc(var(--animationDuration) / 8);
|
||||
|
||||
& circle {
|
||||
animation: fadeInOut var(--animationDuration) infinite linear both;
|
||||
transform-origin: center;
|
||||
|
||||
&:nth-child(2) {
|
||||
animation-delay: calc(var(--delayPerItem) * -7);
|
||||
}
|
||||
&:nth-child(3) {
|
||||
animation-delay: calc(var(--delayPerItem) * -6);
|
||||
}
|
||||
&:nth-child(4) {
|
||||
animation-delay: calc(var(--delayPerItem) * -5);
|
||||
}
|
||||
&:nth-child(5) {
|
||||
animation-delay: calc(var(--delayPerItem) * -4);
|
||||
}
|
||||
&:nth-child(6) {
|
||||
animation-delay: calc(var(--delayPerItem) * -3);
|
||||
}
|
||||
&:nth-child(7) {
|
||||
animation-delay: calc(var(--delayPerItem) * -2);
|
||||
}
|
||||
&:nth-child(8) {
|
||||
animation-delay: calc(var(--delayPerItem) * -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInOut {
|
||||
0%,
|
||||
20%,
|
||||
80%,
|
||||
100% {
|
||||
opacity: 0.3;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@@ -5,14 +5,27 @@ import SectionLink from "../Link"
|
||||
|
||||
import styles from "./header.module.css"
|
||||
|
||||
import type { HeaderProps } from "@/types/components/myPages/header"
|
||||
import type { HeadingProps } from "@/components/TempDesignSystem/Text/Title/title"
|
||||
|
||||
export type HeaderProps = {
|
||||
link?: {
|
||||
href: string
|
||||
text: string
|
||||
}
|
||||
preamble?: string | null
|
||||
textTransform?: HeadingProps["textTransform"]
|
||||
title?: string | null
|
||||
headingLevel?: HeadingProps["level"]
|
||||
headingAs?: HeadingProps["as"]
|
||||
}
|
||||
|
||||
export default function SectionHeader({
|
||||
link,
|
||||
preamble,
|
||||
title,
|
||||
topTitle = false,
|
||||
textTransform,
|
||||
headingAs = "h4",
|
||||
headingLevel = "h2",
|
||||
}: HeaderProps) {
|
||||
if (!title && !preamble && !link) {
|
||||
return null
|
||||
@@ -21,9 +34,9 @@ export default function SectionHeader({
|
||||
return (
|
||||
<header className={styles.header}>
|
||||
<Title
|
||||
as={topTitle ? "h3" : "h4"}
|
||||
as={headingAs}
|
||||
className={styles.title}
|
||||
level={topTitle ? "h1" : "h2"}
|
||||
level={headingLevel}
|
||||
textTransform={textTransform}
|
||||
>
|
||||
{title}
|
||||
|
||||
Reference in New Issue
Block a user